----
@@ -37,7 +47,7 @@ A utility library based on Go 1.18+ generics that makes it easier to work with s
I wanted a **short name**, similar to "Lodash", and no Go package uses this name.
-
+
## 🚀 Install
@@ -47,7 +57,7 @@ go get github.com/samber/lo@v1
This library is v1 and follows SemVer strictly.
-No breaking changes will be made to exported APIs before v2.0.0.
+No breaking changes will be made to exported APIs before v2.0.0, except for experimental packages under `exp/`.
This library has no dependencies outside the Go standard library.
@@ -106,8 +116,11 @@ Supported helpers for slices:
- [GroupBy](#groupby)
- [GroupByMap](#groupbymap)
- [Chunk](#chunk)
+- [Window](#window)
+- [Sliding](#sliding)
- [PartitionBy](#partitionby)
- [Flatten](#flatten)
+- [Concat](#concat)
- [Interleave](#interleave)
- [Shuffle](#shuffle)
- [Reverse](#reverse)
@@ -118,6 +131,9 @@ Supported helpers for slices:
- [SliceToMap / Associate](#slicetomap-alias-associate)
- [FilterSliceToMap](#filterslicetomap)
- [Keyify](#keyify)
+- [Take](#take)
+- [TakeWhile](#takewhile)
+- [TakeFilter](#takefilter)
- [Drop](#drop)
- [DropRight](#dropright)
- [DropWhile](#dropwhile)
@@ -134,9 +150,10 @@ Supported helpers for slices:
- [Slice](#slice)
- [Replace](#replace)
- [ReplaceAll](#replaceall)
+- [Clone](#clone)
- [Compact](#compact)
- [IsSorted](#issorted)
-- [IsSortedByKey](#issortedbykey)
+- [IsSortedBy](#issortedby)
- [Splice](#Splice)
- [Cut](#Cut)
- [CutPrefix](#CutPrefix)
@@ -239,6 +256,7 @@ Supported intersection helpers:
- [None](#none)
- [NoneBy](#noneby)
- [Intersect](#intersect)
+- [IntersectBy](#intersectby)
- [Difference](#difference)
- [Union](#union)
- [Without](#without)
@@ -375,6 +393,17 @@ even := lo.Filter([]int{1, 2, 3, 4}, func(x int, index int) bool {
// []int{2, 4}
```
+```go
+// Use FilterErr when the predicate can return an error
+even, err := lo.FilterErr([]int{1, 2, 3, 4}, func(x int, _ int) (bool, error) {
+ if x == 3 {
+ return false, fmt.Errorf("number 3 is not allowed")
+ }
+ return x%2 == 0, nil
+})
+// []int(nil), error("number 3 is not allowed")
+```
+
[[play](https://go.dev/play/p/Apjg3WeSi7K)]
Mutable: like `lo.Filter()`, but the slice is updated in place.
@@ -407,9 +436,20 @@ lo.Map([]int64{1, 2, 3, 4}, func(x int64, index int) string {
// []string{"1", "2", "3", "4"}
```
+```go
+// Use MapErr when the transform function can return an error
+result, err := lo.MapErr([]int{1, 2, 3, 4}, func(x int, _ int) (string, error) {
+ if x == 3 {
+ return "", fmt.Errorf("number 3 is not allowed")
+ }
+ return strconv.Itoa(x), nil
+})
+// []string(nil), error("number 3 is not allowed")
+```
+
[[play](https://go.dev/play/p/OkPcYAhBo0D)]
-Parallel processing: like `lo.Map()`, but the mapper function is called in a goroutine. Results are returned in the same order.
+Parallel processing: like `lo.Map()`, but the transform function is called in a goroutine. Results are returned in the same order.
```go
import lop "github.com/samber/lo/parallel"
@@ -487,6 +527,17 @@ lo.FlatMap([]int64{0, 1, 2}, func(x int64, _ int) []string {
// []string{"0", "0", "1", "1", "2", "2"}
```
+```go
+// Use FlatMapErr when the transform function can return an error
+result, err := lo.FlatMapErr([]int64{0, 1, 2, 3}, func(x int64, _ int) ([]string, error) {
+ if x == 2 {
+ return nil, fmt.Errorf("number 2 is not allowed")
+ }
+ return []string{strconv.FormatInt(x, 10), strconv.FormatInt(x, 10)}, nil
+})
+// []string(nil), error("number 2 is not allowed")
+```
+
[[play](https://go.dev/play/p/YSoYmQTA8-U)]
### Reduce
@@ -500,6 +551,17 @@ sum := lo.Reduce([]int{1, 2, 3, 4}, func(agg int, item int, _ int) int {
// 10
```
+```go
+// Use ReduceErr when the accumulator function can return an error
+result, err := lo.ReduceErr([]int{1, 2, 3, 4}, func(agg int, item int, _ int) (int, error) {
+ if item == 3 {
+ return 0, fmt.Errorf("number 3 is not allowed")
+ }
+ return agg + item, nil
+}, 0)
+// 0, error("number 3 is not allowed")
+```
+
[[play](https://go.dev/play/p/R4UHXZNaaUG)]
### ReduceRight
@@ -513,6 +575,17 @@ result := lo.ReduceRight([][]int{{0, 1}, {2, 3}, {4, 5}}, func(agg []int, item [
// []int{4, 5, 2, 3, 0, 1}
```
+```go
+// Use ReduceRightErr when the accumulator function can return an error
+result, err := lo.ReduceRightErr([]int{1, 2, 3, 4}, func(agg int, item int, _ int) (int, error) {
+ if item == 2 {
+ return 0, fmt.Errorf("number 2 is not allowed")
+ }
+ return agg + item, nil
+}, 0)
+// 0, error("number 2 is not allowed")
+```
+
[[play](https://go.dev/play/p/Fq3W70l7wXF)]
### ForEach
@@ -609,6 +682,17 @@ uniqValues := lo.UniqBy([]int{0, 1, 2, 3, 4, 5}, func(i int) int {
// []int{0, 1, 2}
```
+```go
+// Use UniqByErr when the iteratee function can return an error
+result, err := lo.UniqByErr([]int{0, 1, 2, 3, 4, 5}, func(i int) (int, error) {
+ if i == 3 {
+ return 0, fmt.Errorf("number 3 is not allowed")
+ }
+ return i % 3, nil
+})
+// []int(nil), error("number 3 is not allowed")
+```
+
[[play](https://go.dev/play/p/g42Z3QSb53u)]
### GroupBy
@@ -624,6 +708,17 @@ groups := lo.GroupBy([]int{0, 1, 2, 3, 4, 5}, func(i int) int {
// map[int][]int{0: []int{0, 3}, 1: []int{1, 4}, 2: []int{2, 5}}
```
+```go
+// Use GroupByErr when the iteratee function can return an error
+result, err := lo.GroupByErr([]int{0, 1, 2, 3, 4, 5}, func(i int) (int, error) {
+ if i == 3 {
+ return 0, fmt.Errorf("number 3 is not allowed")
+ }
+ return i % 3, nil
+})
+// map[int][]int(nil), error("number 3 is not allowed")
+```
+
[[play](https://go.dev/play/p/XnQBd_v6brd)]
Parallel processing: like `lo.GroupBy()`, but callback is called in goroutine.
@@ -650,6 +745,17 @@ groups := lo.GroupByMap([]int{0, 1, 2, 3, 4, 5}, func(i int) (int, int) {
// map[int][]int{0: []int{0, 6}, 1: []int{2, 8}, 2: []int{4, 10}}
```
+```go
+// Use GroupByMapErr when the transform function can return an error
+result, err := lo.GroupByMapErr([]int{0, 1, 2, 3, 4, 5}, func(i int) (int, int, error) {
+ if i == 3 {
+ return 0, 0, fmt.Errorf("number 3 is not allowed")
+ }
+ return i % 3, i * 2, nil
+})
+// map[int][]int(nil), error("number 3 is not allowed")
+```
+
[[play](https://go.dev/play/p/iMeruQ3_W80)]
### Chunk
@@ -672,6 +778,36 @@ lo.Chunk([]int{0}, 2)
[[play](https://go.dev/play/p/kEMkFbdu85g)]
+### Window
+
+Creates a slice of sliding windows of a given size. Each window shares size-1 elements with the previous one. This is equivalent to `Sliding(collection, size, 1)`.
+
+```go
+lo.Window([]int{1, 2, 3, 4, 5}, 3)
+// [][]int{{1, 2, 3}, {2, 3, 4}, {3, 4, 5}}
+
+lo.Window([]float64{20, 22, 21, 23, 24}, 3)
+// [][]float64{{20, 22, 21}, {22, 21, 23}, {21, 23, 24}}
+```
+
+### Sliding
+
+Creates a slice of sliding windows of a given size with a given step. If step is equal to size, windows have no common elements (similar to Chunk). If step is less than size, windows share common elements.
+
+```go
+// Windows with shared elements (step < size)
+lo.Sliding([]int{1, 2, 3, 4, 5, 6}, 3, 1)
+// [][]int{{1, 2, 3}, {2, 3, 4}, {3, 4, 5}, {4, 5, 6}}
+
+// Windows with no shared elements (step == size, like Chunk)
+lo.Sliding([]int{1, 2, 3, 4, 5, 6}, 3, 3)
+// [][]int{{1, 2, 3}, {4, 5, 6}}
+
+// Step > size (skipping elements)
+lo.Sliding([]int{1, 2, 3, 4, 5, 6, 7, 8}, 2, 3)
+// [][]int{{1, 2}, {4, 5}, {7, 8}}
+```
+
### PartitionBy
Returns a slice of elements split into groups. The order of grouped values is determined by the order they occur in collection. The grouping is generated from the results of running each element of collection through iteratee.
@@ -690,6 +826,22 @@ partitions := lo.PartitionBy([]int{-2, -1, 0, 1, 2, 3, 4, 5}, func(x int) string
// [][]int{{-2, -1}, {0, 2, 4}, {1, 3, 5}}
```
+```go
+// Use PartitionByErr when the iteratee function can return an error
+result, err := lo.PartitionByErr([]int{-2, -1, 0, 1, 2}, func(x int) (string, error) {
+ if x == 0 {
+ return "", fmt.Errorf("zero is not allowed")
+ }
+ if x < 0 {
+ return "negative", nil
+ } else if x%2 == 0 {
+ return "even", nil
+ }
+ return "odd", nil
+})
+// [][]int(nil), error("zero is not allowed")
+```
+
[[play](https://go.dev/play/p/NfQ_nGjkgXW)]
Parallel processing: like `lo.PartitionBy()`, but callback is called in goroutine. Results are returned in the same order.
@@ -719,6 +871,20 @@ flat := lo.Flatten([][]int{{0, 1}, {2, 3, 4, 5}})
[[play](https://go.dev/play/p/rbp9ORaMpjw)]
+### Concat
+
+Returns a new slice containing all the elements in collections. Concat conserves the order of the elements.
+
+```go
+slice := lo.Concat([]int{1, 2}, []int{3, 4})
+// []int{1, 2, 3, 4}
+
+slice := lo.Concat(nil, []int{1, 2}, nil, []int{3, 4}, nil)
+// []int{1, 2, 3, 4}
+
+slice := lo.Concat[int]()
+// []int{}
+```
### Interleave
Round-robin alternating input slices and sequentially appending value at index into result.
@@ -825,6 +991,18 @@ slice := lo.RepeatBy(5, func(i int) string {
[[play](https://go.dev/play/p/ozZLCtX_hNU)]
+With error handling:
+
+```go
+slice, err := lo.RepeatByErr(5, func(i int) (string, error) {
+ if i == 3 {
+ return "", fmt.Errorf("index 3 is not allowed")
+ }
+ return fmt.Sprintf("item-%d", i), nil
+})
+// []string(nil), error("index 3 is not allowed")
+```
+
### KeyBy
Transforms a slice or a slice of structs to a map based on a pivot callback.
@@ -849,6 +1027,16 @@ result := lo.KeyBy(characters, func(char Character) string {
//map[a:{dir:left code:97} d:{dir:right code:100}]
```
+```go
+result, err := lo.KeyByErr([]string{"a", "aa", "aaa", ""}, func(str string) (int, error) {
+ if str == "" {
+ return 0, fmt.Errorf("empty string not allowed")
+ }
+ return len(str), nil
+})
+// map[int]string(nil), error("empty string not allowed")
+```
+
[[play](https://go.dev/play/p/mdaClUAT-zZ)]
### SliceToMap (alias: Associate)
@@ -901,6 +1089,50 @@ set := lo.Keyify([]int{1, 1, 2, 3, 4})
[[play](https://go.dev/play/p/RYhhM_csqIG)]
+### Take
+
+Takes the first n elements from a slice.
+
+```go
+l := lo.Take([]int{0, 1, 2, 3, 4, 5}, 3)
+// []int{0, 1, 2}
+
+l := lo.Take([]int{0, 1, 2}, 5)
+// []int{0, 1, 2}
+```
+
+### TakeWhile
+
+Takes elements from the beginning while the predicate returns true.
+
+```go
+l := lo.TakeWhile([]int{0, 1, 2, 3, 4, 5}, func(val int) bool {
+ return val < 3
+})
+// []int{0, 1, 2}
+
+l := lo.TakeWhile([]string{"a", "aa", "aaa", "aa"}, func(val string) bool {
+ return len(val) <= 2
+})
+// []string{"a", "aa"}
+```
+
+### TakeFilter
+
+Filters elements and takes the first n elements that match the predicate. Equivalent to calling Take(Filter(...)), but more efficient as it stops after finding n matches.
+
+```go
+l := lo.TakeFilter([]int{1, 2, 3, 4, 5, 6, 7, 8, 9, 10}, 3, func(val int, index int) bool {
+ return val%2 == 0
+})
+// []int{2, 4, 6}
+
+l := lo.TakeFilter([]string{"a", "aa", "aaa", "aaaa"}, 2, func(val string, index int) bool {
+ return len(val) > 1
+})
+// []string{"aa", "aaa"}
+```
+
### Drop
Drops n elements from the beginning of a slice.
@@ -971,6 +1203,17 @@ odd := lo.Reject([]int{1, 2, 3, 4}, func(x int, _ int) bool {
// []int{1, 3}
```
+```go
+// Use RejectErr when the predicate can return an error
+odd, err := lo.RejectErr([]int{1, 2, 3, 4}, func(x int, _ int) (bool, error) {
+ if x == 3 {
+ return false, fmt.Errorf("number 3 is not allowed")
+ }
+ return x%2 == 0, nil
+})
+// []int(nil), error("number 3 is not allowed")
+```
+
[[play](https://go.dev/play/p/YkLMODy1WEL)]
### RejectMap
@@ -1023,6 +1266,17 @@ count := lo.CountBy([]int{1, 5, 1}, func(i int) bool {
// 2
```
+```go
+// Use CountByErr when the predicate can return an error
+count, err := lo.CountByErr([]int{1, 5, 1}, func(i int) (bool, error) {
+ if i == 5 {
+ return false, fmt.Errorf("5 not allowed")
+ }
+ return i < 4, nil
+})
+// 0, error("5 not allowed")
+```
+
[[play](https://go.dev/play/p/ByQbNYQQi4X)]
### CountValues
@@ -1158,6 +1412,20 @@ slice := lo.ReplaceAll(in, -1, 42)
[[play](https://go.dev/play/p/a9xZFUHfYcV)]
+### Clone
+
+Returns a shallow copy of the collection.
+
+```go
+in := []int{1, 2, 3, 4, 5}
+cloned := lo.Clone(in)
+// Verify it's a different slice by checking that modifying one doesn't affect the other
+in[0] = 99
+// cloned is []int{1, 2, 3, 4, 5}
+```
+
+[[play](https://go.dev/play/p/hgHmoOIxmuH)]
+
### Compact
Returns a slice of all non-zero elements.
@@ -1182,12 +1450,12 @@ slice := lo.IsSorted([]int{0, 1, 2, 3, 4, 5, 6, 7, 8, 9})
[[play](https://go.dev/play/p/mc3qR-t4mcx)]
-### IsSortedByKey
+### IsSortedBy
Checks if a slice is sorted by iteratee.
```go
-slice := lo.IsSortedByKey([]string{"a", "bb", "ccc"}, func(s string) int {
+slice := lo.IsSortedBy([]string{"a", "bb", "ccc"}, func(s string) int {
return len(s)
})
// true
@@ -1455,6 +1723,17 @@ m := lo.PickBy(map[string]int{"foo": 1, "bar": 2, "baz": 3}, func(key string, va
// map[string]int{"foo": 1, "baz": 3}
```
+```go
+// Use PickByErr when the predicate can return an error
+m, err := lo.PickByErr(map[string]int{"foo": 1, "bar": 2, "baz": 3}, func(key string, value int) (bool, error) {
+ if key == "bar" {
+ return false, fmt.Errorf("bar not allowed")
+ }
+ return value%2 == 1, nil
+})
+// map[string]int(nil), error("bar not allowed")
+```
+
[[play](https://go.dev/play/p/kdg8GR_QMmf)]
### PickByKeys
@@ -1490,6 +1769,17 @@ m := lo.OmitBy(map[string]int{"foo": 1, "bar": 2, "baz": 3}, func(key string, va
// map[string]int{"bar": 2}
```
+```go
+// Use OmitByErr when the predicate can return an error
+m, err := lo.OmitByErr(map[string]int{"foo": 1, "bar": 2, "baz": 3}, func(key string, value int) (bool, error) {
+ if key == "bar" {
+ return false, fmt.Errorf("bar not allowed")
+ }
+ return value%2 == 1, nil
+})
+// map[string]int(nil), error("bar not allowed")
+```
+
[[play](https://go.dev/play/p/EtBsR43bdsd)]
### OmitByKeys
@@ -1615,6 +1905,17 @@ m2 := lo.MapKeys(map[int]int{1: 1, 2: 2, 3: 3, 4: 4}, func(_ int, v int) string
// map[string]int{"1": 1, "2": 2, "3": 3, "4": 4}
```
+```go
+// Use MapKeysErr when the iteratee can return an error
+m2, err := lo.MapKeysErr(map[int]int{1: 1, 2: 2, 3: 3}, func(_ int, v int) (string, error) {
+ if v == 2 {
+ return "", fmt.Errorf("even number not allowed")
+ }
+ return strconv.FormatInt(int64(v), 10), nil
+})
+// map[string]int(nil), error("even number not allowed")
+```
+
[[play](https://go.dev/play/p/9_4WPIqOetJ)]
### MapValues
@@ -1630,6 +1931,18 @@ m2 := lo.MapValues(m1, func(x int64, _ int) string {
// map[int]string{1: "1", 2: "2", 3: "3"}
```
+```go
+// Use MapValuesErr when the iteratee can return an error
+m1 := map[int]int64{1: 1, 2: 2, 3: 3}
+m2, err := lo.MapValuesErr(m1, func(x int64, _ int) (string, error) {
+ if x == 2 {
+ return "", fmt.Errorf("even number not allowed")
+ }
+ return strconv.FormatInt(x, 10), nil
+})
+// map[int]string(nil), error("even number not allowed")
+```
+
[[play](https://go.dev/play/p/T_8xAfvcf0W)]
### MapEntries
@@ -1645,6 +1958,18 @@ out := lo.MapEntries(in, func(k string, v int) (int, string) {
// map[int]string{1: "foo", 2: "bar"}
```
+```go
+// Use MapEntriesErr when the iteratee can return an error
+in := map[string]int{"foo": 1, "bar": 2, "baz": 3}
+out, err := lo.MapEntriesErr(in, func(k string, v int) (int, string, error) {
+ if k == "bar" {
+ return 0, "", fmt.Errorf("bar not allowed")
+ }
+ return v, k, nil
+})
+// map[int]string(nil), error("bar not allowed")
+```
+
[[play](https://go.dev/play/p/VuvNQzxKimT)]
### MapToSlice
@@ -1660,6 +1985,18 @@ s := lo.MapToSlice(m, func(k int, v int64) string {
// []string{"1_4", "2_5", "3_6"}
```
+```go
+// Use MapToSliceErr when the iteratee can return an error
+m := map[int]int64{1: 4, 2: 5, 3: 6}
+s, err := lo.MapToSliceErr(m, func(k int, v int64) (string, error) {
+ if k == 2 {
+ return "", fmt.Errorf("key 2 not allowed")
+ }
+ return fmt.Sprintf("%d_%d", k, v), nil
+})
+// []string(nil), error("key 2 not allowed")
+```
+
[[play](https://go.dev/play/p/ZuiCZpDt6LD)]
### FilterMapToSlice
@@ -1677,6 +2014,18 @@ result := lo.FilterMapToSlice(kv, func(k int, v int64) (string, bool) {
// []{"2_2", "4_4"}
```
+```go
+kv := map[int]int64{1: 1, 2: 2, 3: 3, 4: 4}
+
+result, err := lo.FilterMapToSliceErr(kv, func(k int, v int64) (string, bool, error) {
+ if k == 3 {
+ return "", false, fmt.Errorf("key 3 not allowed")
+ }
+ return fmt.Sprintf("%d_%d", k, v), k%2 == 0, nil
+})
+// []string(nil), error("key 3 not allowed")
+```
+
### FilterKeys
Transforms a map into a slice based on predicate returns true for specific elements. It is a mix of `lo.Filter()` and `lo.Keys()`.
@@ -1690,6 +2039,17 @@ result := FilterKeys(kv, func(k int, v string) bool {
// [1]
```
+```go
+// Use FilterKeysErr when the predicate can return an error
+result, err := lo.FilterKeysErr(map[int]string{1: "foo", 2: "bar", 3: "baz"}, func(k int, v string) (bool, error) {
+ if k == 3 {
+ return false, fmt.Errorf("key 3 not allowed")
+ }
+ return v == "foo", nil
+})
+// []int(nil), error("key 3 not allowed")
+```
+
[[play](https://go.dev/play/p/OFlKXlPrBAe)]
### FilterValues
@@ -1705,6 +2065,17 @@ result := FilterValues(kv, func(k int, v string) bool {
// ["foo"]
```
+```go
+// Use FilterValuesErr when the predicate can return an error
+result, err := lo.FilterValuesErr(map[int]string{1: "foo", 2: "bar", 3: "baz"}, func(k int, v string) (bool, error) {
+ if k == 3 {
+ return false, fmt.Errorf("key 3 not allowed")
+ }
+ return v == "foo", nil
+})
+// []string(nil), error("key 3 not allowed")
+```
+
[[play](https://go.dev/play/p/YVD5r_h-LX-)]
### Range / RangeFrom / RangeWithSteps
@@ -1784,6 +2155,19 @@ sum := lo.SumBy(strings, func(item string) int {
// 6
```
+With error handling:
+
+```go
+strings := []string{"foo", "bar", "baz"}
+sum, err := lo.SumByErr(strings, func(item string) (int, error) {
+ if item == "bar" {
+ return 0, fmt.Errorf("invalid item: %s", item)
+ }
+ return len(item), nil
+})
+// sum: 3, err: invalid item: bar
+```
+
### Product
Calculates the product of the values in a collection.
@@ -1812,6 +2196,18 @@ product := lo.ProductBy(strings, func(item string) int {
// 9
```
+```go
+// Use ProductByErr when the transform function can return an error
+strings := []string{"foo", "bar", "baz"}
+product, err := lo.ProductByErr(strings, func(item string) (int, error) {
+ if item == "bar" {
+ return 0, fmt.Errorf("bar is not allowed")
+ }
+ return len(item), nil
+})
+// 3, error("bar is not allowed")
+```
+
[[play](https://go.dev/play/p/wadzrWr9Aer)]
### Mean
@@ -1850,6 +2246,18 @@ mean := lo.MeanBy([]float64{}, mapper)
// 0
```
+```go
+// Use MeanByErr when the transform function can return an error
+list := []string{"aa", "bbb", "cccc", "ddddd"}
+mean, err := lo.MeanByErr(list, func(item string) (float64, error) {
+ if item == "cccc" {
+ return 0, fmt.Errorf("cccc is not allowed")
+ }
+ return float64(len(item)), nil
+})
+// 0, error("cccc is not allowed")
+```
+
[[play](https://go.dev/play/p/j7TsVwBOZ7P)]
### Mode
@@ -1920,6 +2328,8 @@ lo.ChunkString("1", 2)
// []string{"1"}
```
+Note: `lo.ChunkString` and `lo.Chunk` functions behave inconsistently for empty input: `lo.ChunkString("", n)` returns `[""]` instead of `[]`. See [#788](https://github.com/samber/lo/issues/788).
+
[[play](https://go.dev/play/p/__FLTuJVz54)]
### RuneLength
@@ -2004,7 +2414,7 @@ str := lo.Capitalize("heLLO")
### Ellipsis
-Trims and truncates a string to a specified length in `bytes` and appends an ellipsis if truncated. If the string contains non-ASCII characters (which may occupy multiple bytes in UTF-8), truncating by byte length may split a character in the middle, potentially resulting in garbled output.
+Trims and truncates a string to a specified length in runes (Unicode code points) and appends an ellipsis if truncated. Multi-byte characters such as emoji or CJK ideographs are never split in the middle.
```go
str := lo.Ellipsis(" Lorem Ipsum ", 5)
@@ -2015,6 +2425,12 @@ str := lo.Ellipsis("Lorem Ipsum", 100)
str := lo.Ellipsis("Lorem Ipsum", 3)
// ...
+
+str := lo.Ellipsis("hello 世界! 你好", 8)
+// hello...
+
+str := lo.Ellipsis("🏠🐶🐱🌟", 4)
+// 🏠🐶🐱🌟
```
[[play](https://go.dev/play/p/qE93rgqe1TW)]
@@ -2079,6 +2495,18 @@ items := lo.ZipBy2([]string{"a", "b"}, []int{1, 2}, func(a string, b int) string
// []string{"a-1", "b-2"}
```
+With error handling:
+
+```go
+items, err := lo.ZipByErr2([]string{"a", "b"}, []int{1, 2}, func(a string, b int) (string, error) {
+ if b == 2 {
+ return "", fmt.Errorf("number 2 is not allowed")
+ }
+ return fmt.Sprintf("%s-%d", a, b), nil
+})
+// []string(nil), error("number 2 is not allowed")
+```
+
### Unzip2 -> Unzip9
Unzip accepts a slice of grouped elements and creates a slice regrouping the elements to their pre-zip configuration.
@@ -2103,6 +2531,18 @@ a, b := lo.UnzipBy2([]string{"hello", "john", "doe"}, func(str string) (string,
// []int{5, 4, 3}
```
+```go
+a, b, err := lo.UnzipByErr2([]string{"hello", "error", "world"}, func(str string) (string, int, error) {
+ if str == "error" {
+ return "", 0, fmt.Errorf("error string not allowed")
+ }
+ return str, len(str), nil
+})
+// []string{}
+// []int{}
+// error string not allowed
+```
+
### CrossJoin2 -> CrossJoin9
Combines every item from one list with every item from others. It is the cartesian product of lists received as arguments. Returns an empty list if a list is empty.
@@ -2119,7 +2559,7 @@ result := lo.CrossJoin2([]string{"hello", "john", "doe"}, []int{1, 2})
### CrossJoinBy2 -> CrossJoinBy9
-Combines every item from one list with every item from others. It is the cartesian product of lists received as arguments. The project function is used to create the output values. Returns an empty list if a list is empty.
+Combines every item from one list with every item from others. It is the cartesian product of lists received as arguments. The transform function is used to create the output values. Returns an empty list if a list is empty.
```go
result := lo.CrossJoinBy2([]string{"hello", "john", "doe"}, []int{1, 2}, func(a A, b B) string {
@@ -2133,6 +2573,18 @@ result := lo.CrossJoinBy2([]string{"hello", "john", "doe"}, []int{1, 2}, func(a
// "doe - 2"
```
+With error handling:
+
+```go
+result, err := lo.CrossJoinByErr2([]string{"hello", "john"}, []int{1, 2}, func(a string, b int) (string, error) {
+ if a == "john" {
+ return "", fmt.Errorf("john not allowed")
+ }
+ return fmt.Sprintf("%s - %d", a, b), nil
+})
+// []string(nil), error("john not allowed")
+```
+
### Duration
Returns the time taken to execute a function.
@@ -2550,7 +3002,7 @@ b := NoneBy([]int{1, 2, 3, 4}, func(x int) bool {
### Intersect
-Returns the intersection between two collections.
+Returns the intersection between collections.
```go
result1 := lo.Intersect([]int{0, 1, 2, 3, 4, 5}, []int{0, 2})
@@ -2561,6 +3013,31 @@ result2 := lo.Intersect([]int{0, 1, 2, 3, 4, 5}, []int{0, 6})
result3 := lo.Intersect([]int{0, 1, 2, 3, 4, 5}, []int{-1, 6})
// []int{}
+
+result4 := lo.Intersect([]int{0, 3, 5, 7}, []int{3, 5}, []int{0, 1, 2, 0, 3, 0})
+// []int{3}
+```
+
+### IntersectBy
+
+Returns the intersection between two collections using a custom key selector function.
+
+```go
+transform := func(v int) string {
+ return strconv.Itoa(v)
+}
+
+result1 := lo.IntersectBy(transform, []int{0, 1, 2, 3, 4, 5}, []int{0, 2})
+// []int{0, 2}
+
+result2 := lo.IntersectBy(transform, []int{0, 1, 2, 3, 4, 5}, []int{0, 6})
+// []int{0}
+
+result3 := lo.IntersectBy(transform, []int{0, 1, 2, 3, 4, 5}, []int{-1, 6})
+// []int{}
+
+result4 := lo.IntersectBy(transform, []int{0, 3, 5, 7}, []int{3, 5}, []int{0, 1, 2, 0, 3, 0})
+// []int{3}
```
### Difference
@@ -2608,7 +3085,7 @@ Filters a slice by excluding elements whose extracted keys match any in the excl
Returns a new slice containing only the elements whose keys are not in the exclude list.
```go
-type struct User {
+type User struct {
ID int
Name string
}
@@ -2633,6 +3110,30 @@ filteredUsers := lo.WithoutBy(users, getID, excludedIDs...)
// []User[{ID: 1, Name: "Alice"}]
```
+```go
+// Use WithoutByErr when the iteratee can return an error
+type struct User {
+ ID int
+ Name string
+}
+
+users := []User{
+ {ID: 1, Name: "Alice"},
+ {ID: 2, Name: "Bob"},
+ {ID: 3, Name: "Charlie"},
+}
+
+getID := func(user User) (int, error) {
+ if user.ID == 2 {
+ return 0, fmt.Errorf("Bob not allowed")
+ }
+ return user.ID, nil
+}
+
+filteredUsers, err := lo.WithoutByErr(users, getID, 2, 3)
+// []User(nil), error("Bob not allowed")
+```
+
### WithoutEmpty
Returns a slice excluding zero values.
@@ -2751,6 +3252,25 @@ str, ok := lo.Find([]string{"foobar"}, func(i string) bool {
// "", false
```
+```go
+// Use FindErr when the predicate can return an error
+str, err := lo.FindErr([]string{"a", "b", "c", "d"}, func(i string) (bool, error) {
+ if i == "c" {
+ return false, fmt.Errorf("c is not allowed")
+ }
+ return i == "b", nil
+})
+// "b", nil
+
+str, err = lo.FindErr([]string{"a", "b", "c"}, func(i string) (bool, error) {
+ if i == "b" {
+ return false, fmt.Errorf("b is not allowed")
+ }
+ return i == "b", nil
+})
+// "", error("b is not allowed")
+```
+
[[play](https://go.dev/play/p/Eo7W0lvKTky)]
### FindIndexOf
@@ -2879,6 +3399,18 @@ duplicatedValues := lo.FindDuplicatesBy([]int{3, 4, 5, 6, 7}, func(i int) int {
// []int{3, 4}
```
+With error handling:
+
+```go
+duplicatedValues, err := lo.FindDuplicatesByErr([]int{3, 4, 5, 6, 7}, func(i int) (int, error) {
+ if i == 5 {
+ return 0, fmt.Errorf("number 5 is not allowed")
+ }
+ return i % 3, nil
+})
+// []int(nil), error("number 5 is not allowed")
+```
+
### Min
Search the minimum value of a collection.
@@ -2935,6 +3467,17 @@ min := lo.MinBy([]string{}, func(item string, min string) bool {
// ""
```
+```go
+// Use MinByErr when the comparison function can return an error
+min, err := lo.MinByErr([]string{"s1", "string2", "s3"}, func(item string, min string) (bool, error) {
+ if item == "string2" {
+ return false, fmt.Errorf("string2 is not allowed")
+ }
+ return len(item) < len(min), nil
+})
+// "s1", error("string2 is not allowed")
+```
+
### MinIndexBy
Search the minimum value of a collection using the given comparison function and the index of the minimum value.
@@ -2955,6 +3498,16 @@ min, index := lo.MinIndexBy([]string{}, func(item string, min string) bool {
// "", -1
```
+```go
+min, index, err := lo.MinIndexByErr([]string{"s1", "string2", "s3"}, func(item string, min string) (bool, error) {
+ if item == "s2" {
+ return false, fmt.Errorf("s2 is not allowed")
+ }
+ return len(item) < len(min), nil
+})
+// "s1", 0, error("s2 is not allowed")
+```
+
### Earliest
Search the minimum time.Time of a collection.
@@ -2983,6 +3536,17 @@ earliest := lo.EarliestBy([]foo{{time.Now()}, {}}, func(i foo) time.Time {
// {bar:{2023-04-01 01:02:03 +0000 UTC}}
```
+```go
+// Use EarliestByErr when the iteratee function can return an error
+earliest, err := lo.EarliestByErr([]foo{{time.Now()}, {}}, func(i foo) (time.Time, error) {
+ if i.bar.IsZero() {
+ return time.Time{}, fmt.Errorf("zero time not allowed")
+ }
+ return i.bar, nil
+})
+// {bar:{...}}, error("zero time not allowed")
+```
+
### Max
Search the maximum value of a collection.
@@ -3037,6 +3601,19 @@ max := lo.MaxBy([]string{}, func(item string, max string) bool {
// ""
```
+```go
+// Use MaxByErr when the comparison function can return an error
+max, err := lo.MaxByErr([]string{"string1", "s2", "string3"}, func(item string, max string) (bool, error) {
+ if item == "s2" {
+ return false, fmt.Errorf("s2 is not allowed")
+ }
+ return len(item) > len(max), nil
+})
+// "string1", error("s2 is not allowed")
+```
+
+[[play](https://go.dev/play/p/JW1qu-ECwF7)]
+
### MaxIndexBy
Search the maximum value of a collection using the given comparison function and the index of the maximum value.
@@ -3057,6 +3634,19 @@ max, index := lo.MaxIndexBy([]string{}, func(item string, max string) bool {
// "", -1
```
+```go
+// Use MaxIndexByErr when the comparison function can return an error
+max, index, err := lo.MaxIndexByErr([]string{"string1", "s2", "string3"}, func(item string, max string) (bool, error) {
+ if item == "s2" {
+ return false, fmt.Errorf("s2 is not allowed")
+ }
+ return len(item) > len(max), nil
+})
+// "string1", 0, error("s2 is not allowed")
+```
+
+[[play](https://go.dev/play/p/uaUszc-c9QK)]
+
### Latest
Search the maximum time.Time of a collection.
@@ -3085,6 +3675,17 @@ latest := lo.LatestBy([]foo{{time.Now()}, {}}, func(i foo) time.Time {
// {bar:{2023-04-01 01:02:03 +0000 UTC}}
```
+```go
+// Use LatestByErr when the iteratee function can return an error
+result, err := lo.LatestByErr([]foo{{time.Now()}, {}}, func(i foo) (time.Time, error) {
+ if i.bar.IsZero() {
+ return time.Time{}, fmt.Errorf("zero time not allowed")
+ }
+ return i.bar, nil
+})
+// foo{}, error("zero time not allowed")
+```
+
### First
Returns the first element of a collection and check for availability of the first element.
diff --git a/vendor/github.com/samber/lo/channel.go b/vendor/github.com/samber/lo/channel.go
index 9a10852..5c7dd9e 100644
--- a/vendor/github.com/samber/lo/channel.go
+++ b/vendor/github.com/samber/lo/channel.go
@@ -26,12 +26,7 @@ func ChannelDispatcher[T any](stream <-chan T, count, channelBufferCap int, stra
var i uint64
- for {
- msg, ok := <-stream
- if !ok {
- return
- }
-
+ for msg := range stream {
destination := strategy(msg, i, roChildren) % count
children[destination] <- msg
@@ -107,8 +102,8 @@ func DispatchingStrategyRandom[T any](msg T, index uint64, channels []<-chan T)
func DispatchingStrategyWeightedRandom[T any](weights []int) DispatchingStrategy[T] {
seq := []int{}
- for i := 0; i < len(weights); i++ {
- for j := 0; j < weights[i]; j++ {
+ for i, weight := range weights {
+ for j := 0; j < weight; j++ {
seq = append(seq, i)
}
}
@@ -143,22 +138,22 @@ func DispatchingStrategyFirst[T any](msg T, index uint64, channels []<-chan T) i
// DispatchingStrategyLeast distributes messages in the emptiest channel.
// Play: https://go.dev/play/p/ypy0jrRcEe7
func DispatchingStrategyLeast[T any](msg T, index uint64, channels []<-chan T) int {
- seq := Range(len(channels))
-
- return MinBy(seq, func(item, mIn int) bool {
- return len(channels[item]) < len(channels[mIn])
+ _, i := MinIndexBy(channels, func(a, b <-chan T) bool {
+ return len(a) < len(b)
})
+
+ return i
}
// DispatchingStrategyMost distributes messages in the fullest channel.
// If the channel capacity is exceeded, the next channel will be selected and so on.
// Play: https://go.dev/play/p/erHHone7rF9
func DispatchingStrategyMost[T any](msg T, index uint64, channels []<-chan T) int {
- seq := Range(len(channels))
-
- return MaxBy(seq, func(item, mAx int) bool {
- return len(channels[item]) > len(channels[mAx]) && channelIsNotFull(channels[item])
+ _, i := MaxIndexBy(channels, func(a, b <-chan T) bool {
+ return len(a) > len(b) && channelIsNotFull(a)
})
+
+ return i
}
// SliceToChannel returns a read-only channel of collection elements.
@@ -213,10 +208,9 @@ func Generator[T any](bufferSize int, generator func(yield func(T))) <-chan T {
// Play: https://go.dev/play/p/gPQ-6xmcKQI
func Buffer[T any](ch <-chan T, size int) (collection []T, length int, readTime time.Duration, ok bool) {
buffer := make([]T, 0, size)
- index := 0
now := time.Now()
- for ; index < size; index++ {
+ for index := 0; index < size; index++ {
item, ok := <-ch
if !ok {
return buffer, index, time.Since(now), false
@@ -225,14 +219,7 @@ func Buffer[T any](ch <-chan T, size int) (collection []T, length int, readTime
buffer = append(buffer, item)
}
- return buffer, index, time.Since(now), true
-}
-
-// Batch creates a slice of n elements from a channel. Returns the slice and the slice length.
-//
-// Deprecated: Use [Buffer] instead.
-func Batch[T any](ch <-chan T, size int) (collection []T, length int, readTime time.Duration, ok bool) {
- return Buffer(ch, size)
+ return buffer, size, time.Since(now), true
}
// BufferWithContext creates a slice of n elements from a channel, with context. Returns the slice and the slice length.
@@ -267,13 +254,6 @@ func BufferWithTimeout[T any](ch <-chan T, size int, timeout time.Duration) (col
return BufferWithContext(ctx, ch, size)
}
-// BatchWithTimeout creates a slice of n elements from a channel, with timeout. Returns the slice and the slice length.
-//
-// Deprecated: Use [BufferWithTimeout] instead.
-func BatchWithTimeout[T any](ch <-chan T, size int, timeout time.Duration) (collection []T, length int, readTime time.Duration, ok bool) {
- return BufferWithTimeout(ch, size, timeout)
-}
-
// FanIn collects messages from multiple input channels into a single buffered channel.
// Output messages have no priority. When all upstream channels reach EOF, downstream channel closes.
// Play: https://go.dev/play/p/FH8Wq-T04Jb
@@ -300,14 +280,6 @@ func FanIn[T any](channelBufferCap int, upstreams ...<-chan T) <-chan T {
return out
}
-// ChannelMerge collects messages from multiple input channels into a single buffered channel.
-// Output messages have no priority. When all upstream channels reach EOF, downstream channel closes.
-//
-// Deprecated: Use [FanIn] instead.
-func ChannelMerge[T any](channelBufferCap int, upstreams ...<-chan T) <-chan T {
- return FanIn(channelBufferCap, upstreams...)
-}
-
// FanOut broadcasts all the upstream messages to multiple downstream channels.
// When upstream channel reaches EOF, downstream channels close. If any downstream
// channels is full, broadcasting is paused.
diff --git a/vendor/github.com/samber/lo/concurrency.go b/vendor/github.com/samber/lo/concurrency.go
index 35e1f74..70871b2 100644
--- a/vendor/github.com/samber/lo/concurrency.go
+++ b/vendor/github.com/samber/lo/concurrency.go
@@ -10,9 +10,9 @@ type synchronize struct {
locker sync.Locker
}
-func (s *synchronize) Do(cb func()) {
+func (s *synchronize) Do(callback func()) {
s.locker.Lock()
- Try0(cb)
+ Try0(callback)
s.locker.Unlock()
}
@@ -52,13 +52,13 @@ func Async0(f func()) <-chan struct{} {
}
// Async1 is an alias to Async.
-// Play: https://go.dev/play/p/uo35gosuTLw
+// Play: https://go.dev/play/p/RBQWtIn4PsF
func Async1[A any](f func() A) <-chan A {
return Async(f)
}
// Async2 has the same behavior as Async, but returns the 2 results as a tuple inside the channel.
-// Play: https://go.dev/play/p/7W7mKQi0AhA
+// Play: https://go.dev/play/p/5SzzDjssXOH
func Async2[A, B any](f func() (A, B)) <-chan Tuple2[A, B] {
ch := make(chan Tuple2[A, B], 1)
go func() {
@@ -68,7 +68,7 @@ func Async2[A, B any](f func() (A, B)) <-chan Tuple2[A, B] {
}
// Async3 has the same behavior as Async, but returns the 3 results as a tuple inside the channel.
-// Play: https://go.dev/play/p/L1d6o6l6q0d
+// Play: https://go.dev/play/p/cZpZsDXNmlx
func Async3[A, B, C any](f func() (A, B, C)) <-chan Tuple3[A, B, C] {
ch := make(chan Tuple3[A, B, C], 1)
go func() {
@@ -78,7 +78,7 @@ func Async3[A, B, C any](f func() (A, B, C)) <-chan Tuple3[A, B, C] {
}
// Async4 has the same behavior as Async, but returns the 4 results as a tuple inside the channel.
-// Play: https://go.dev/play/p/1X7q6oL0TqF
+// Play: https://go.dev/play/p/9X5O2VrLzkR
func Async4[A, B, C, D any](f func() (A, B, C, D)) <-chan Tuple4[A, B, C, D] {
ch := make(chan Tuple4[A, B, C, D], 1)
go func() {
@@ -88,7 +88,7 @@ func Async4[A, B, C, D any](f func() (A, B, C, D)) <-chan Tuple4[A, B, C, D] {
}
// Async5 has the same behavior as Async, but returns the 5 results as a tuple inside the channel.
-// Play: https://go.dev/play/p/2W7q4oL1TqG
+// Play: https://go.dev/play/p/MqnUJpkmopA
func Async5[A, B, C, D, E any](f func() (A, B, C, D, E)) <-chan Tuple5[A, B, C, D, E] {
ch := make(chan Tuple5[A, B, C, D, E], 1)
go func() {
@@ -98,7 +98,7 @@ func Async5[A, B, C, D, E any](f func() (A, B, C, D, E)) <-chan Tuple5[A, B, C,
}
// Async6 has the same behavior as Async, but returns the 6 results as a tuple inside the channel.
-// Play: https://go.dev/play/p/3X8q5pM2UrH
+// Play: https://go.dev/play/p/kM1X67JPdSP
func Async6[A, B, C, D, E, F any](f func() (A, B, C, D, E, F)) <-chan Tuple6[A, B, C, D, E, F] {
ch := make(chan Tuple6[A, B, C, D, E, F], 1)
go func() {
diff --git a/vendor/github.com/samber/lo/condition.go b/vendor/github.com/samber/lo/condition.go
index 1eb017e..92ed36b 100644
--- a/vendor/github.com/samber/lo/condition.go
+++ b/vendor/github.com/samber/lo/condition.go
@@ -121,9 +121,9 @@ func (s *switchCase[T, R]) Case(val T, result R) *switchCase[T, R] {
// CaseF.
// Play: https://go.dev/play/p/TGbKUMAeRUd
-func (s *switchCase[T, R]) CaseF(val T, cb func() R) *switchCase[T, R] {
+func (s *switchCase[T, R]) CaseF(val T, callback func() R) *switchCase[T, R] {
if !s.done && s.predicate == val {
- s.result = cb()
+ s.result = callback()
s.done = true
}
@@ -142,9 +142,9 @@ func (s *switchCase[T, R]) Default(result R) R {
// DefaultF.
// Play: https://go.dev/play/p/TGbKUMAeRUd
-func (s *switchCase[T, R]) DefaultF(cb func() R) R {
+func (s *switchCase[T, R]) DefaultF(callback func() R) R {
if !s.done {
- s.result = cb()
+ s.result = callback()
}
return s.result
diff --git a/vendor/github.com/samber/lo/errors.go b/vendor/github.com/samber/lo/errors.go
index 135230a..8313edd 100644
--- a/vendor/github.com/samber/lo/errors.go
+++ b/vendor/github.com/samber/lo/errors.go
@@ -30,8 +30,8 @@ func messageFromMsgAndArgs(msgAndArgs ...any) string {
return ""
}
-// must panics if err is error or false.
-func must(err any, messageArgs ...any) {
+// MustChecker panics if err is error or false.
+var MustChecker = func(err any, messageArgs ...any) {
if err == nil {
return
}
@@ -63,14 +63,14 @@ func must(err any, messageArgs ...any) {
// and panics if err is error or false.
// Play: https://go.dev/play/p/fOqtX5HudtN
func Must[T any](val T, err any, messageArgs ...any) T {
- must(err, messageArgs...)
+ MustChecker(err, messageArgs...)
return val
}
// Must0 has the same behavior as Must, but callback returns no variable.
// Play: https://go.dev/play/p/TMoWrRp3DyC
func Must0(err any, messageArgs ...any) {
- must(err, messageArgs...)
+ MustChecker(err, messageArgs...)
}
// Must1 is an alias to Must.
@@ -82,35 +82,35 @@ func Must1[T any](val T, err any, messageArgs ...any) T {
// Must2 has the same behavior as Must, but callback returns 2 variables.
// Play: https://go.dev/play/p/TMoWrRp3DyC
func Must2[T1, T2 any](val1 T1, val2 T2, err any, messageArgs ...any) (T1, T2) {
- must(err, messageArgs...)
+ MustChecker(err, messageArgs...)
return val1, val2
}
// Must3 has the same behavior as Must, but callback returns 3 variables.
// Play: https://go.dev/play/p/TMoWrRp3DyC
func Must3[T1, T2, T3 any](val1 T1, val2 T2, val3 T3, err any, messageArgs ...any) (T1, T2, T3) {
- must(err, messageArgs...)
+ MustChecker(err, messageArgs...)
return val1, val2, val3
}
// Must4 has the same behavior as Must, but callback returns 4 variables.
// Play: https://go.dev/play/p/TMoWrRp3DyC
func Must4[T1, T2, T3, T4 any](val1 T1, val2 T2, val3 T3, val4 T4, err any, messageArgs ...any) (T1, T2, T3, T4) {
- must(err, messageArgs...)
+ MustChecker(err, messageArgs...)
return val1, val2, val3, val4
}
// Must5 has the same behavior as Must, but callback returns 5 variables.
// Play: https://go.dev/play/p/TMoWrRp3DyC
func Must5[T1, T2, T3, T4, T5 any](val1 T1, val2 T2, val3 T3, val4 T4, val5 T5, err any, messageArgs ...any) (T1, T2, T3, T4, T5) {
- must(err, messageArgs...)
+ MustChecker(err, messageArgs...)
return val1, val2, val3, val4, val5
}
// Must6 has the same behavior as Must, but callback returns 6 variables.
// Play: https://go.dev/play/p/TMoWrRp3DyC
func Must6[T1, T2, T3, T4, T5, T6 any](val1 T1, val2 T2, val3 T3, val4 T4, val5 T5, val6 T6, err any, messageArgs ...any) (T1, T2, T3, T4, T5, T6) {
- must(err, messageArgs...)
+ MustChecker(err, messageArgs...)
return val1, val2, val3, val4, val5, val6
}
@@ -356,7 +356,7 @@ func ErrorsAs[T error](err error) (T, bool) {
// Assert does nothing when the condition is true, otherwise it panics with an optional message.
// Play: https://go.dev/play/p/Xv8LLKBMNwI
-func Assert(condition bool, message ...string) {
+var Assert = func(condition bool, message ...string) {
if condition {
return
}
@@ -370,7 +370,7 @@ func Assert(condition bool, message ...string) {
// Assertf does nothing when the condition is true, otherwise it panics with a formatted message.
// Play: https://go.dev/play/p/TVPEmVcyrdY
-func Assertf(condition bool, format string, args ...any) {
+var Assertf = func(condition bool, format string, args ...any) {
if condition {
return
}
diff --git a/vendor/github.com/samber/lo/find.go b/vendor/github.com/samber/lo/find.go
index 534b4d7..f5be852 100644
--- a/vendor/github.com/samber/lo/find.go
+++ b/vendor/github.com/samber/lo/find.go
@@ -1,7 +1,6 @@
package lo
import (
- "fmt"
"time"
"github.com/samber/lo/internal/constraints"
@@ -81,6 +80,26 @@ func Find[T any](collection []T, predicate func(item T) bool) (T, bool) {
return result, false
}
+// FindErr searches for an element in a slice based on a predicate that can return an error.
+// Returns the element and nil error if the element is found.
+// Returns zero value and nil error if the element is not found.
+// If the predicate returns an error, iteration stops immediately and returns zero value and the error.
+func FindErr[T any](collection []T, predicate func(item T) (bool, error)) (T, error) {
+ for i := range collection {
+ matches, err := predicate(collection[i])
+ if err != nil {
+ var result T
+ return result, err
+ }
+ if matches {
+ return collection[i], nil
+ }
+ }
+
+ var result T
+ return result, nil
+}
+
// FindIndexOf searches for an element in a slice based on a predicate and returns the index and true.
// Returns -1 and false if the element is not found.
// Play: https://go.dev/play/p/XWSEM4Ic_t0
@@ -152,16 +171,20 @@ func FindKeyBy[K comparable, V any](object map[K]V, predicate func(key K, value
func FindUniques[T comparable, Slice ~[]T](collection Slice) Slice {
isDupl := make(map[T]bool, len(collection))
+ duplicates := 0
+
for i := range collection {
- duplicated, ok := isDupl[collection[i]]
- if !ok {
- isDupl[collection[i]] = false
- } else if !duplicated {
- isDupl[collection[i]] = true
+ duplicated, seen := isDupl[collection[i]]
+ if !duplicated {
+ isDupl[collection[i]] = seen
+
+ if seen {
+ duplicates++
+ }
}
}
- result := make(Slice, 0, len(collection)-len(isDupl))
+ result := make(Slice, 0, len(isDupl)-duplicates)
for i := range collection {
if duplicated := isDupl[collection[i]]; !duplicated {
@@ -178,18 +201,22 @@ func FindUniques[T comparable, Slice ~[]T](collection Slice) Slice {
func FindUniquesBy[T any, U comparable, Slice ~[]T](collection Slice, iteratee func(item T) U) Slice {
isDupl := make(map[U]bool, len(collection))
+ duplicates := 0
+
for i := range collection {
key := iteratee(collection[i])
- duplicated, ok := isDupl[key]
- if !ok {
- isDupl[key] = false
- } else if !duplicated {
- isDupl[key] = true
+ duplicated, seen := isDupl[key]
+ if !duplicated {
+ isDupl[key] = seen
+
+ if seen {
+ duplicates++
+ }
}
}
- result := make(Slice, 0, len(collection)-len(isDupl))
+ result := make(Slice, 0, len(isDupl)-duplicates)
for i := range collection {
key := iteratee(collection[i])
@@ -207,16 +234,20 @@ func FindUniquesBy[T any, U comparable, Slice ~[]T](collection Slice, iteratee f
func FindDuplicates[T comparable, Slice ~[]T](collection Slice) Slice {
isDupl := make(map[T]bool, len(collection))
+ duplicates := 0
+
for i := range collection {
- duplicated, ok := isDupl[collection[i]]
- if !ok {
- isDupl[collection[i]] = false
- } else if !duplicated {
- isDupl[collection[i]] = true
+ duplicated, seen := isDupl[collection[i]]
+ if !duplicated {
+ isDupl[collection[i]] = seen
+
+ if seen {
+ duplicates++
+ }
}
}
- result := make(Slice, 0, len(collection)-len(isDupl))
+ result := make(Slice, 0, duplicates)
for i := range collection {
if duplicated := isDupl[collection[i]]; duplicated {
@@ -234,18 +265,22 @@ func FindDuplicates[T comparable, Slice ~[]T](collection Slice) Slice {
func FindDuplicatesBy[T any, U comparable, Slice ~[]T](collection Slice, iteratee func(item T) U) Slice {
isDupl := make(map[U]bool, len(collection))
+ duplicates := 0
+
for i := range collection {
key := iteratee(collection[i])
- duplicated, ok := isDupl[key]
- if !ok {
- isDupl[key] = false
- } else if !duplicated {
- isDupl[key] = true
+ duplicated, seen := isDupl[key]
+ if !duplicated {
+ isDupl[key] = seen
+
+ if seen {
+ duplicates++
+ }
}
}
- result := make(Slice, 0, len(collection)-len(isDupl))
+ result := make(Slice, 0, duplicates)
for i := range collection {
key := iteratee(collection[i])
@@ -259,6 +294,52 @@ func FindDuplicatesBy[T any, U comparable, Slice ~[]T](collection Slice, iterate
return result
}
+// FindDuplicatesByErr returns a slice with the first occurrence of each duplicated element in the collection.
+// The order of result values is determined by the order they occur in the slice. It accepts `iteratee` which is
+// invoked for each element in the slice to generate the criterion by which uniqueness is computed.
+// If the iteratee returns an error, iteration stops immediately and the error is returned with a nil slice.
+func FindDuplicatesByErr[T any, U comparable, Slice ~[]T](collection Slice, iteratee func(item T) (U, error)) (Slice, error) {
+ isDupl := make(map[U]bool, len(collection))
+
+ duplicates := 0
+
+ // First pass: identify duplicates
+ for i := range collection {
+ key, err := iteratee(collection[i])
+ if err != nil {
+ var result Slice
+ return result, err
+ }
+
+ duplicated, seen := isDupl[key]
+ if !duplicated {
+ isDupl[key] = seen
+
+ if seen {
+ duplicates++
+ }
+ }
+ }
+
+ result := make(Slice, 0, duplicates)
+
+ // Second pass: collect first occurrences of duplicates
+ for i := range collection {
+ key, err := iteratee(collection[i])
+ if err != nil {
+ var result Slice
+ return result, err
+ }
+
+ if duplicated := isDupl[key]; duplicated {
+ result = append(result, collection[i])
+ isDupl[key] = false
+ }
+ }
+
+ return result, nil
+}
+
// Min search the minimum value of a collection.
// Returns zero value when the collection is empty.
// Play: https://go.dev/play/p/r6e-Z8JozS8
@@ -311,7 +392,7 @@ func MinIndex[T constraints.Ordered](collection []T) (T, int) {
// MinBy search the minimum value of a collection using the given comparison function.
// If several values of the collection are equal to the smallest value, returns the first such value.
// Returns zero value when the collection is empty.
-func MinBy[T any](collection []T, comparison func(a, b T) bool) T {
+func MinBy[T any](collection []T, less func(a, b T) bool) T {
var mIn T
if len(collection) == 0 {
@@ -323,7 +404,7 @@ func MinBy[T any](collection []T, comparison func(a, b T) bool) T {
for i := 1; i < len(collection); i++ {
item := collection[i]
- if comparison(item, mIn) {
+ if less(item, mIn) {
mIn = item
}
}
@@ -331,10 +412,39 @@ func MinBy[T any](collection []T, comparison func(a, b T) bool) T {
return mIn
}
+// MinByErr search the minimum value of a collection using the given comparison function.
+// If several values of the collection are equal to the smallest value, returns the first such value.
+// Returns zero value and nil error when the collection is empty.
+// If the comparison function returns an error, iteration stops and the error is returned.
+func MinByErr[T any](collection []T, less func(a, b T) (bool, error)) (T, error) {
+ var mIn T
+
+ if len(collection) == 0 {
+ return mIn, nil
+ }
+
+ mIn = collection[0]
+
+ for i := 1; i < len(collection); i++ {
+ item := collection[i]
+
+ isLess, err := less(item, mIn)
+ if err != nil {
+ var zero T
+ return zero, err
+ }
+ if isLess {
+ mIn = item
+ }
+ }
+
+ return mIn, nil
+}
+
// MinIndexBy search the minimum value of a collection using the given comparison function and the index of the minimum value.
// If several values of the collection are equal to the smallest value, returns the first such value.
// Returns (zero value, -1) when the collection is empty.
-func MinIndexBy[T any](collection []T, comparison func(a, b T) bool) (T, int) {
+func MinIndexBy[T any](collection []T, less func(a, b T) bool) (T, int) {
var (
mIn T
index int
@@ -349,7 +459,7 @@ func MinIndexBy[T any](collection []T, comparison func(a, b T) bool) (T, int) {
for i := 1; i < len(collection); i++ {
item := collection[i]
- if comparison(item, mIn) {
+ if less(item, mIn) {
mIn = item
index = i
}
@@ -358,6 +468,40 @@ func MinIndexBy[T any](collection []T, comparison func(a, b T) bool) (T, int) {
return mIn, index
}
+// MinIndexByErr search the minimum value of a collection using the given comparison function and the index of the minimum value.
+// If several values of the collection are equal to the smallest value, returns the first such value.
+// Returns (zero value, -1) when the collection is empty.
+// Comparison function can return an error to stop iteration immediately.
+func MinIndexByErr[T any](collection []T, less func(a, b T) (bool, error)) (T, int, error) {
+ var (
+ mIn T
+ index int
+ )
+
+ if len(collection) == 0 {
+ return mIn, -1, nil
+ }
+
+ mIn = collection[0]
+
+ for i := 1; i < len(collection); i++ {
+ item := collection[i]
+
+ isLess, err := less(item, mIn)
+ if err != nil {
+ var zero T
+ return zero, -1, err
+ }
+
+ if isLess {
+ mIn = item
+ index = i
+ }
+ }
+
+ return mIn, index, nil
+}
+
// Earliest search the minimum time.Time of a collection.
// Returns zero value when the collection is empty.
func Earliest(times ...time.Time) time.Time {
@@ -404,6 +548,37 @@ func EarliestBy[T any](collection []T, iteratee func(item T) time.Time) T {
return earliest
}
+// EarliestByErr search the minimum time.Time of a collection using the given iteratee function.
+// Returns zero value and nil error when the collection is empty.
+// If the iteratee returns an error, iteration stops and the error is returned.
+func EarliestByErr[T any](collection []T, iteratee func(item T) (time.Time, error)) (T, error) {
+ var earliest T
+
+ if len(collection) == 0 {
+ return earliest, nil
+ }
+
+ earliestTime, err := iteratee(collection[0])
+ if err != nil {
+ return earliest, err
+ }
+ earliest = collection[0]
+
+ for i := 1; i < len(collection); i++ {
+ itemTime, err := iteratee(collection[i])
+ if err != nil {
+ return earliest, err
+ }
+
+ if itemTime.Before(earliestTime) {
+ earliest = collection[i]
+ earliestTime = itemTime
+ }
+ }
+
+ return earliest, nil
+}
+
// Max searches the maximum value of a collection.
// Returns zero value when the collection is empty.
// Play: https://go.dev/play/p/r6e-Z8JozS8
@@ -456,7 +631,12 @@ func MaxIndex[T constraints.Ordered](collection []T) (T, int) {
// MaxBy search the maximum value of a collection using the given comparison function.
// If several values of the collection are equal to the greatest value, returns the first such value.
// Returns zero value when the collection is empty.
-func MaxBy[T any](collection []T, comparison func(a, b T) bool) T {
+//
+// Note: the comparison function is inconsistent with most languages, since we use the opposite of the usual convention.
+// See https://github.com/samber/lo/issues/129
+//
+// Play: https://go.dev/play/p/JW1qu-ECwF7
+func MaxBy[T any](collection []T, greater func(a, b T) bool) T {
var mAx T
if len(collection) == 0 {
@@ -468,7 +648,7 @@ func MaxBy[T any](collection []T, comparison func(a, b T) bool) T {
for i := 1; i < len(collection); i++ {
item := collection[i]
- if comparison(item, mAx) {
+ if greater(item, mAx) {
mAx = item
}
}
@@ -476,10 +656,46 @@ func MaxBy[T any](collection []T, comparison func(a, b T) bool) T {
return mAx
}
+// MaxByErr search the maximum value of a collection using the given comparison function.
+// If several values of the collection are equal to the greatest value, returns the first such value.
+// Returns zero value and nil error when the collection is empty.
+// If the comparison function returns an error, iteration stops and the error is returned.
+//
+// Note: the comparison function is inconsistent with most languages, since we use the opposite of the usual convention.
+// See https://github.com/samber/lo/issues/129
+func MaxByErr[T any](collection []T, greater func(a, b T) (bool, error)) (T, error) {
+ var mAx T
+
+ if len(collection) == 0 {
+ return mAx, nil
+ }
+
+ mAx = collection[0]
+
+ for i := 1; i < len(collection); i++ {
+ item := collection[i]
+
+ isGreater, err := greater(item, mAx)
+ if err != nil {
+ return mAx, err
+ }
+ if isGreater {
+ mAx = item
+ }
+ }
+
+ return mAx, nil
+}
+
// MaxIndexBy search the maximum value of a collection using the given comparison function and the index of the maximum value.
// If several values of the collection are equal to the greatest value, returns the first such value.
// Returns (zero value, -1) when the collection is empty.
-func MaxIndexBy[T any](collection []T, comparison func(a, b T) bool) (T, int) {
+//
+// Note: the comparison function is inconsistent with most languages, since we use the opposite of the usual convention.
+// See https://github.com/samber/lo/issues/129
+//
+// Play: https://go.dev/play/p/uaUszc-c9QK
+func MaxIndexBy[T any](collection []T, greater func(a, b T) bool) (T, int) {
var (
mAx T
index int
@@ -494,7 +710,7 @@ func MaxIndexBy[T any](collection []T, comparison func(a, b T) bool) (T, int) {
for i := 1; i < len(collection); i++ {
item := collection[i]
- if comparison(item, mAx) {
+ if greater(item, mAx) {
mAx = item
index = i
}
@@ -503,6 +719,42 @@ func MaxIndexBy[T any](collection []T, comparison func(a, b T) bool) (T, int) {
return mAx, index
}
+// MaxIndexByErr search the maximum value of a collection using the given comparison function and the index of the maximum value.
+// If several values of the collection are equal to the greatest value, returns the first such value.
+// Returns (zero value, -1, nil) when the collection is empty.
+// If the comparison function returns an error, iteration stops and the error is returned.
+//
+// Note: the comparison function is inconsistent with most languages, since we use the opposite of the usual convention.
+// See https://github.com/samber/lo/issues/129
+func MaxIndexByErr[T any](collection []T, greater func(a, b T) (bool, error)) (T, int, error) {
+ var (
+ mAx T
+ index int
+ )
+
+ if len(collection) == 0 {
+ return mAx, -1, nil
+ }
+
+ mAx = collection[0]
+
+ for i := 1; i < len(collection); i++ {
+ item := collection[i]
+
+ isGreater, err := greater(item, mAx)
+ if err != nil {
+ var zero T
+ return zero, -1, err
+ }
+ if isGreater {
+ mAx = item
+ index = i
+ }
+ }
+
+ return mAx, index, nil
+}
+
// Latest search the maximum time.Time of a collection.
// Returns zero value when the collection is empty.
func Latest(times ...time.Time) time.Time {
@@ -549,6 +801,37 @@ func LatestBy[T any](collection []T, iteratee func(item T) time.Time) T {
return latest
}
+// LatestByErr search the maximum time.Time of a collection using the given iteratee function.
+// Returns zero value and nil error when the collection is empty.
+// If the iteratee returns an error, iteration stops and the error is returned.
+func LatestByErr[T any](collection []T, iteratee func(item T) (time.Time, error)) (T, error) {
+ var latest T
+
+ if len(collection) == 0 {
+ return latest, nil
+ }
+
+ latestTime, err := iteratee(collection[0])
+ if err != nil {
+ return latest, err
+ }
+ latest = collection[0]
+
+ for i := 1; i < len(collection); i++ {
+ itemTime, err := iteratee(collection[i])
+ if err != nil {
+ return latest, err
+ }
+
+ if itemTime.After(latestTime) {
+ latest = collection[i]
+ latestTime = itemTime
+ }
+ }
+
+ return latest, nil
+}
+
// First returns the first element of a collection and check for availability of the first element.
// Play: https://go.dev/play/p/ul45Z0y2EFO
func First[T any](collection []T) (T, bool) {
@@ -615,17 +898,22 @@ func LastOr[T any](collection []T, fallback T) T {
// from the end is returned. An error is returned when nth is out of slice bounds.
// Play: https://go.dev/play/p/sHoh88KWt6B
func Nth[T any, N constraints.Integer](collection []T, nth N) (T, error) {
+ value, ok := sliceNth(collection, nth)
+
+ return value, Validate(ok, "nth: %d out of slice bounds", nth)
+}
+
+func sliceNth[T any, N constraints.Integer](collection []T, nth N) (T, bool) {
n := int(nth)
l := len(collection)
if n >= l || -n > l {
- var t T
- return t, fmt.Errorf("nth: %d out of slice bounds", n)
+ return Empty[T](), false
}
if n >= 0 {
- return collection[n], nil
+ return collection[n], true
}
- return collection[l+n], nil
+ return collection[l+n], true
}
// NthOr returns the element at index `nth` of collection.
@@ -633,8 +921,8 @@ func Nth[T any, N constraints.Integer](collection []T, nth N) (T, error) {
// If `nth` is out of slice bounds, it returns the fallback value instead of an error.
// Play: https://go.dev/play/p/sHoh88KWt6B
func NthOr[T any, N constraints.Integer](collection []T, nth N, fallback T) T {
- value, err := Nth(collection, nth)
- if err != nil {
+ value, ok := sliceNth(collection, nth)
+ if !ok {
return fallback
}
return value
@@ -645,11 +933,7 @@ func NthOr[T any, N constraints.Integer](collection []T, nth N, fallback T) T {
// If `nth` is out of slice bounds, it returns the zero value (empty value) for that type.
// Play: https://go.dev/play/p/sHoh88KWt6B
func NthOrEmpty[T any, N constraints.Integer](collection []T, nth N) T {
- value, err := Nth(collection, nth)
- if err != nil {
- var zeroValue T
- return zeroValue
- }
+ value, _ := sliceNth(collection, nth)
return value
}
@@ -660,8 +944,7 @@ type randomIntGenerator func(n int) int
// Sample returns a random item from collection.
// Play: https://go.dev/play/p/vCcSJbh5s6l
func Sample[T any](collection []T) T {
- result := SampleBy(collection, xrand.IntN)
- return result
+ return SampleBy(collection, xrand.IntN)
}
// SampleBy returns a random item from collection, using randomIntGenerator as the random index generator.
@@ -677,29 +960,35 @@ func SampleBy[T any](collection []T, randomIntGenerator randomIntGenerator) T {
// Samples returns N random unique items from collection.
// Play: https://go.dev/play/p/vCcSJbh5s6l
func Samples[T any, Slice ~[]T](collection Slice, count int) Slice {
- results := SamplesBy(collection, count, xrand.IntN)
- return results
+ return SamplesBy(collection, count, xrand.IntN)
}
// SamplesBy returns N random unique items from collection, using randomIntGenerator as the random index generator.
// Play: https://go.dev/play/p/HDmKmMgq0XN
func SamplesBy[T any, Slice ~[]T](collection Slice, count int, randomIntGenerator randomIntGenerator) Slice {
+ if count <= 0 {
+ return Slice{}
+ }
+
size := len(collection)
- cOpy := append(Slice{}, collection...)
+ if size < count {
+ count = size
+ }
- results := Slice{}
+ indexes := Range(size)
+ results := make(Slice, count)
- for i := 0; i < size && i < count; i++ {
- copyLength := size - i
+ for i := range results {
+ n := len(indexes)
- index := randomIntGenerator(size - i)
- results = append(results, cOpy[index])
+ index := randomIntGenerator(n)
+ results[i] = collection[indexes[index]]
- // Removes element.
+ // Removes index.
// It is faster to swap with last element and remove it.
- cOpy[index] = cOpy[copyLength-1]
- cOpy = cOpy[:copyLength-1]
+ indexes[index] = indexes[n-1]
+ indexes = indexes[:n-1]
}
return results
diff --git a/vendor/github.com/samber/lo/intersect.go b/vendor/github.com/samber/lo/intersect.go
index 6c79f99..469cbbb 100644
--- a/vendor/github.com/samber/lo/intersect.go
+++ b/vendor/github.com/samber/lo/intersect.go
@@ -27,8 +27,14 @@ func ContainsBy[T any](collection []T, predicate func(item T) bool) bool {
// Every returns true if all elements of a subset are contained in a collection or if the subset is empty.
// Play: https://go.dev/play/p/W1EvyqY6t9j
func Every[T comparable](collection, subset []T) bool {
- for i := range subset {
- if !Contains(collection, subset[i]) {
+ if len(subset) == 0 {
+ return true
+ }
+
+ seen := Keyify(collection)
+
+ for _, item := range subset {
+ if _, ok := seen[item]; !ok {
return false
}
}
@@ -52,8 +58,13 @@ func EveryBy[T any](collection []T, predicate func(item T) bool) bool {
// If the subset is empty Some returns false.
// Play: https://go.dev/play/p/Lj4ceFkeT9V
func Some[T comparable](collection, subset []T) bool {
- for i := range subset {
- if Contains(collection, subset[i]) {
+ if len(subset) == 0 {
+ return false
+ }
+
+ seen := Keyify(subset)
+ for i := range collection {
+ if _, ok := seen[collection[i]]; ok {
return true
}
}
@@ -77,8 +88,13 @@ func SomeBy[T any](collection []T, predicate func(item T) bool) bool {
// None returns true if no element of a subset is contained in a collection or if the subset is empty.
// Play: https://go.dev/play/p/fye7JsmxzPV
func None[T comparable](collection, subset []T) bool {
- for i := range subset {
- if Contains(collection, subset[i]) {
+ if len(subset) == 0 {
+ return true
+ }
+
+ seen := Keyify(subset)
+ for i := range collection {
+ if _, ok := seen[collection[i]]; ok {
return false
}
}
@@ -98,19 +114,88 @@ func NoneBy[T any](collection []T, predicate func(item T) bool) bool {
return true
}
-// Intersect returns the intersection between two collections.
+// Intersect returns the intersection between collections.
// Play: https://go.dev/play/p/uuElL9X9e58
-func Intersect[T comparable, Slice ~[]T](list1, list2 Slice) Slice {
- result := Slice{}
- seen := map[T]struct{}{}
+func Intersect[T comparable, Slice ~[]T](lists ...Slice) Slice {
+ if len(lists) == 0 {
+ return Slice{}
+ }
- for i := range list1 {
- seen[list1[i]] = struct{}{}
+ last := lists[len(lists)-1]
+
+ seen := make(map[T]bool, len(last))
+
+ for _, item := range last {
+ seen[item] = false
}
- for i := range list2 {
- if _, ok := seen[list2[i]]; ok {
- result = append(result, list2[i])
+ for i := len(lists) - 2; i > 0 && len(seen) != 0; i-- {
+ for _, item := range lists[i] {
+ if _, ok := seen[item]; ok {
+ seen[item] = true
+ }
+ }
+
+ for k, v := range seen {
+ if v {
+ seen[k] = false
+ } else {
+ delete(seen, k)
+ }
+ }
+ }
+
+ result := make(Slice, 0, len(seen))
+
+ for _, item := range lists[0] {
+ if _, ok := seen[item]; ok {
+ result = append(result, item)
+ delete(seen, item)
+ }
+ }
+
+ return result
+}
+
+// IntersectBy returns the intersection between two collections using a custom key selector function.
+func IntersectBy[T any, K comparable, Slice ~[]T](transform func(T) K, lists ...Slice) Slice {
+ if len(lists) == 0 {
+ return Slice{}
+ }
+
+ last := lists[len(lists)-1]
+
+ seen := make(map[K]bool, len(last))
+
+ for _, item := range last {
+ k := transform(item)
+ seen[k] = false
+ }
+
+ for i := len(lists) - 2; i > 0 && len(seen) != 0; i-- {
+ for _, item := range lists[i] {
+ k := transform(item)
+ if _, ok := seen[k]; ok {
+ seen[k] = true
+ }
+ }
+
+ for k, v := range seen {
+ if v {
+ seen[k] = false
+ } else {
+ delete(seen, k)
+ }
+ }
+ }
+
+ result := make(Slice, 0, len(seen))
+
+ for _, item := range lists[0] {
+ k := transform(item)
+ if _, ok := seen[k]; ok {
+ result = append(result, item)
+ delete(seen, k)
}
}
@@ -125,16 +210,8 @@ func Difference[T comparable, Slice ~[]T](list1, list2 Slice) (Slice, Slice) {
left := Slice{}
right := Slice{}
- seenLeft := map[T]struct{}{}
- seenRight := map[T]struct{}{}
-
- for i := range list1 {
- seenLeft[list1[i]] = struct{}{}
- }
-
- for i := range list2 {
- seenRight[list2[i]] = struct{}{}
- }
+ seenLeft := Keyify(list1)
+ seenRight := Keyify(list2)
for i := range list1 {
if _, ok := seenRight[list1[i]]; !ok {
@@ -179,10 +256,7 @@ func Union[T comparable, Slice ~[]T](lists ...Slice) Slice {
// Without returns a slice excluding all given values.
// Play: https://go.dev/play/p/5j30Ux8TaD0
func Without[T comparable, Slice ~[]T](collection Slice, exclude ...T) Slice {
- excludeMap := make(map[T]struct{}, len(exclude))
- for i := range exclude {
- excludeMap[exclude[i]] = struct{}{}
- }
+ excludeMap := Keyify(exclude)
result := make(Slice, 0, len(collection))
for i := range collection {
@@ -197,10 +271,7 @@ func Without[T comparable, Slice ~[]T](collection Slice, exclude ...T) Slice {
// Returns a new slice containing only the elements whose keys are not in the exclude list.
// Play: https://go.dev/play/p/VgWJOF01NbJ
func WithoutBy[T any, K comparable, Slice ~[]T](collection Slice, iteratee func(item T) K, exclude ...K) Slice {
- excludeMap := make(map[K]struct{}, len(exclude))
- for _, e := range exclude {
- excludeMap[e] = struct{}{}
- }
+ excludeMap := Keyify(exclude)
result := make(Slice, 0, len(collection))
for _, item := range collection {
@@ -211,6 +282,24 @@ func WithoutBy[T any, K comparable, Slice ~[]T](collection Slice, iteratee func(
return result
}
+// WithoutByErr filters a slice by excluding elements whose extracted keys match any in the exclude list.
+// It returns the first error returned by the iteratee.
+func WithoutByErr[T any, K comparable, Slice ~[]T](collection Slice, iteratee func(item T) (K, error), exclude ...K) (Slice, error) {
+ excludeMap := Keyify(exclude)
+
+ result := make(Slice, 0, len(collection))
+ for _, item := range collection {
+ key, err := iteratee(item)
+ if err != nil {
+ return nil, err
+ }
+ if _, ok := excludeMap[key]; !ok {
+ result = append(result, item)
+ }
+ }
+ return result, nil
+}
+
// WithoutEmpty returns a slice excluding zero values.
//
// Deprecated: Use lo.Compact instead.
@@ -220,15 +309,8 @@ func WithoutEmpty[T comparable, Slice ~[]T](collection Slice) Slice {
// WithoutNth returns a slice excluding the nth value.
// Play: https://go.dev/play/p/5g3F9R2H1xL
-func WithoutNth[T comparable, Slice ~[]T](collection Slice, nths ...int) Slice {
- length := len(collection)
-
- toRemove := make(map[int]struct{}, len(nths))
- for i := range nths {
- if nths[i] >= 0 && nths[i] <= length-1 {
- toRemove[nths[i]] = struct{}{}
- }
- }
+func WithoutNth[T any, Slice ~[]T](collection Slice, nths ...int) Slice {
+ toRemove := Keyify(nths)
result := make(Slice, 0, len(collection))
for i := range collection {
diff --git a/vendor/github.com/samber/lo/map.go b/vendor/github.com/samber/lo/map.go
index cd0e9d7..e5c90e6 100644
--- a/vendor/github.com/samber/lo/map.go
+++ b/vendor/github.com/samber/lo/map.go
@@ -112,6 +112,22 @@ func PickBy[K comparable, V any, Map ~map[K]V](in Map, predicate func(key K, val
return r
}
+// PickByErr returns same map type filtered by given predicate.
+// It returns the first error returned by the predicate.
+func PickByErr[K comparable, V any, Map ~map[K]V](in Map, predicate func(key K, value V) (bool, error)) (Map, error) {
+ r := Map{}
+ for k, v := range in {
+ ok, err := predicate(k, v)
+ if err != nil {
+ return nil, err
+ }
+ if ok {
+ r[k] = v
+ }
+ }
+ return r, nil
+}
+
// PickByKeys returns same map type filtered by given keys.
// Play: https://go.dev/play/p/R1imbuci9qU
func PickByKeys[K comparable, V any, Map ~map[K]V](in Map, keys []K) Map {
@@ -128,8 +144,10 @@ func PickByKeys[K comparable, V any, Map ~map[K]V](in Map, keys []K) Map {
// Play: https://go.dev/play/p/1zdzSvbfsJc
func PickByValues[K, V comparable, Map ~map[K]V](in Map, values []V) Map {
r := Map{}
+
+ seen := Keyify(values)
for k, v := range in {
- if Contains(values, v) {
+ if _, ok := seen[v]; ok {
r[k] = v
}
}
@@ -148,6 +166,22 @@ func OmitBy[K comparable, V any, Map ~map[K]V](in Map, predicate func(key K, val
return r
}
+// OmitByErr returns same map type filtered by given predicate.
+// It returns the first error returned by the predicate.
+func OmitByErr[K comparable, V any, Map ~map[K]V](in Map, predicate func(key K, value V) (bool, error)) (Map, error) {
+ r := Map{}
+ for k, v := range in {
+ ok, err := predicate(k, v)
+ if err != nil {
+ return nil, err
+ }
+ if !ok {
+ r[k] = v
+ }
+ }
+ return r, nil
+}
+
// OmitByKeys returns same map type filtered by given keys.
// Play: https://go.dev/play/p/t1QjCrs-ysk
func OmitByKeys[K comparable, V any, Map ~map[K]V](in Map, keys []K) Map {
@@ -165,11 +199,14 @@ func OmitByKeys[K comparable, V any, Map ~map[K]V](in Map, keys []K) Map {
// Play: https://go.dev/play/p/9UYZi-hrs8j
func OmitByValues[K, V comparable, Map ~map[K]V](in Map, values []V) Map {
r := Map{}
+
+ seen := Keyify(values)
for k, v := range in {
- if !Contains(values, v) {
+ if _, ok := seen[v]; !ok {
r[k] = v
}
}
+
return r
}
@@ -259,12 +296,7 @@ func ChunkEntries[K comparable, V any](m map[K]V, size int) []map[K]V {
return []map[K]V{}
}
- chunksNum := count / size
- if count%size != 0 {
- chunksNum++
- }
-
- result := make([]map[K]V, 0, chunksNum)
+ result := make([]map[K]V, 0, ((count-1)/size)+1)
for k, v := range m {
if len(result) == 0 || len(result[len(result)-1]) == size {
@@ -289,6 +321,22 @@ func MapKeys[K comparable, V any, R comparable](in map[K]V, iteratee func(value
return result
}
+// MapKeysErr manipulates map keys and transforms it to a map of another type.
+// It returns the first error returned by the iteratee.
+func MapKeysErr[K comparable, V any, R comparable](in map[K]V, iteratee func(value V, key K) (R, error)) (map[R]V, error) {
+ result := make(map[R]V, len(in))
+
+ for k, v := range in {
+ r, err := iteratee(v, k)
+ if err != nil {
+ return nil, err
+ }
+ result[r] = v
+ }
+
+ return result, nil
+}
+
// MapValues manipulates map values and transforms it to a map of another type.
// Play: https://go.dev/play/p/T_8xAfvcf0W
func MapValues[K comparable, V, R any](in map[K]V, iteratee func(value V, key K) R) map[K]R {
@@ -301,6 +349,22 @@ func MapValues[K comparable, V, R any](in map[K]V, iteratee func(value V, key K)
return result
}
+// MapValuesErr manipulates map values and transforms it to a map of another type.
+// It returns the first error returned by the iteratee.
+func MapValuesErr[K comparable, V, R any](in map[K]V, iteratee func(value V, key K) (R, error)) (map[K]R, error) {
+ result := make(map[K]R, len(in))
+
+ for k, v := range in {
+ r, err := iteratee(v, k)
+ if err != nil {
+ return nil, err
+ }
+ result[k] = r
+ }
+
+ return result, nil
+}
+
// MapEntries manipulates map entries and transforms it to a map of another type.
// Play: https://go.dev/play/p/VuvNQzxKimT
func MapEntries[K1 comparable, V1 any, K2 comparable, V2 any](in map[K1]V1, iteratee func(key K1, value V1) (K2, V2)) map[K2]V2 {
@@ -314,6 +378,22 @@ func MapEntries[K1 comparable, V1 any, K2 comparable, V2 any](in map[K1]V1, iter
return result
}
+// MapEntriesErr manipulates map entries and transforms it to a map of another type.
+// It returns the first error returned by the iteratee.
+func MapEntriesErr[K1 comparable, V1 any, K2 comparable, V2 any](in map[K1]V1, iteratee func(key K1, value V1) (K2, V2, error)) (map[K2]V2, error) {
+ result := make(map[K2]V2, len(in))
+
+ for k1 := range in {
+ k2, v2, err := iteratee(k1, in[k1])
+ if err != nil {
+ return nil, err
+ }
+ result[k2] = v2
+ }
+
+ return result, nil
+}
+
// MapToSlice transforms a map into a slice based on specified iteratee.
// Play: https://go.dev/play/p/ZuiCZpDt6LD
func MapToSlice[K comparable, V, R any](in map[K]V, iteratee func(key K, value V) R) []R {
@@ -326,6 +406,22 @@ func MapToSlice[K comparable, V, R any](in map[K]V, iteratee func(key K, value V
return result
}
+// MapToSliceErr transforms a map into a slice based on specified iteratee.
+// It returns the first error returned by the iteratee.
+func MapToSliceErr[K comparable, V, R any](in map[K]V, iteratee func(key K, value V) (R, error)) ([]R, error) {
+ result := make([]R, 0, len(in))
+
+ for k, v := range in {
+ r, err := iteratee(k, v)
+ if err != nil {
+ return nil, err
+ }
+ result = append(result, r)
+ }
+
+ return result, nil
+}
+
// FilterMapToSlice transforms a map into a slice based on specified iteratee.
// The iteratee returns a value and a boolean. If the boolean is true, the value is added to the result slice.
// If the boolean is false, the value is not added to the result slice.
@@ -343,6 +439,27 @@ func FilterMapToSlice[K comparable, V, R any](in map[K]V, iteratee func(key K, v
return result
}
+// FilterMapToSliceErr transforms a map into a slice based on specified iteratee.
+// The iteratee returns a value, a boolean, and an error. If the boolean is true, the value is added to the result slice.
+// If the boolean is false, the value is not added to the result slice.
+// If an error is returned, iteration stops immediately and returns the error.
+// The order of the keys in the input map is not specified and the order of the keys in the output slice is not guaranteed.
+func FilterMapToSliceErr[K comparable, V, R any](in map[K]V, iteratee func(key K, value V) (R, bool, error)) ([]R, error) {
+ result := make([]R, 0, len(in))
+
+ for k, v := range in {
+ r, ok, err := iteratee(k, v)
+ if err != nil {
+ return nil, err
+ }
+ if ok {
+ result = append(result, r)
+ }
+ }
+
+ return result, nil
+}
+
// FilterKeys transforms a map into a slice based on predicate returns true for specific elements.
// It is a mix of lo.Filter() and lo.Keys().
// Play: https://go.dev/play/p/OFlKXlPrBAe
@@ -372,3 +489,45 @@ func FilterValues[K comparable, V any](in map[K]V, predicate func(key K, value V
return result
}
+
+// FilterKeysErr transforms a map into a slice of keys based on predicate that can return an error.
+// It is a mix of lo.Filter() and lo.Keys() with error handling.
+// If the predicate returns true, the key is added to the result slice.
+// If the predicate returns an error, iteration stops immediately and returns the error.
+// The order of the keys in the input map is not specified.
+func FilterKeysErr[K comparable, V any](in map[K]V, predicate func(key K, value V) (bool, error)) ([]K, error) {
+ result := make([]K, 0)
+
+ for k, v := range in {
+ ok, err := predicate(k, v)
+ if err != nil {
+ return nil, err
+ }
+ if ok {
+ result = append(result, k)
+ }
+ }
+
+ return result, nil
+}
+
+// FilterValuesErr transforms a map into a slice of values based on predicate that can return an error.
+// It is a mix of lo.Filter() and lo.Values() with error handling.
+// If the predicate returns true, the value is added to the result slice.
+// If the predicate returns an error, iteration stops immediately and returns the error.
+// The order of the keys in the input map is not specified.
+func FilterValuesErr[K comparable, V any](in map[K]V, predicate func(key K, value V) (bool, error)) ([]V, error) {
+ result := make([]V, 0)
+
+ for k, v := range in {
+ ok, err := predicate(k, v)
+ if err != nil {
+ return nil, err
+ }
+ if ok {
+ result = append(result, v)
+ }
+ }
+
+ return result, nil
+}
diff --git a/vendor/github.com/samber/lo/math.go b/vendor/github.com/samber/lo/math.go
index 9342b59..f08f227 100644
--- a/vendor/github.com/samber/lo/math.go
+++ b/vendor/github.com/samber/lo/math.go
@@ -1,15 +1,17 @@
package lo
import (
+ "math"
+
"github.com/samber/lo/internal/constraints"
)
// Range creates a slice of numbers (positive and/or negative) with given length.
// Play: https://go.dev/play/p/0r6VimXAi9H
func Range(elementNum int) []int {
- length := If(elementNum < 0, -elementNum).Else(elementNum)
+ step := Ternary(elementNum < 0, -1, 1)
+ length := elementNum * step
result := make([]int, length)
- step := If(elementNum < 0, -1).Else(1)
for i, j := 0, 0; i < length; i, j = i+1, j+step {
result[i] = j
}
@@ -19,9 +21,9 @@ func Range(elementNum int) []int {
// RangeFrom creates a slice of numbers from start with specified length.
// Play: https://go.dev/play/p/0r6VimXAi9H
func RangeFrom[T constraints.Integer | constraints.Float](start T, elementNum int) []T {
- length := If(elementNum < 0, -elementNum).Else(elementNum)
+ step := Ternary(elementNum < 0, -1, 1)
+ length := elementNum * step
result := make([]T, length)
- step := If(elementNum < 0, -1).Else(1)
for i, j := 0, start; i < length; i, j = i+1, j+T(step) {
result[i] = j
}
@@ -32,22 +34,32 @@ func RangeFrom[T constraints.Integer | constraints.Float](start T, elementNum in
// step set to zero will return an empty slice.
// Play: https://go.dev/play/p/0r6VimXAi9H
func RangeWithSteps[T constraints.Integer | constraints.Float](start, end, step T) []T {
- result := []T{}
if start == end || step == 0 {
- return result
+ return []T{}
+ }
+
+ capacity := func(count, delta T) int {
+ // Use math.Ceil instead of (count-1)/delta+1 because integer division
+ // fails for floats (e.g., 5.5/2.5=2.2 → ceil=3, not 2).
+ return int(math.Ceil(float64(count) / float64(delta)))
}
+
if start < end {
if step < 0 {
- return result
+ return []T{}
}
+
+ result := make([]T, 0, capacity(end-start, step))
for i := start; i < end; i += step {
result = append(result, i)
}
return result
}
if step > 0 {
- return result
+ return []T{}
}
+
+ result := make([]T, 0, capacity(start-end, -step))
for i := start; i > end; i += step {
result = append(result, i)
}
@@ -85,17 +97,24 @@ func SumBy[T any, R constraints.Float | constraints.Integer | constraints.Comple
return sum
}
+// SumByErr summarizes the values in a collection using the given return value from the iteration function.
+// If the iteratee returns an error, iteration stops and the error is returned.
+// If collection is empty 0 and nil error are returned.
+func SumByErr[T any, R constraints.Float | constraints.Integer | constraints.Complex](collection []T, iteratee func(item T) (R, error)) (R, error) {
+ var sum R
+ for i := range collection {
+ v, err := iteratee(collection[i])
+ if err != nil {
+ return sum, err
+ }
+ sum += v
+ }
+ return sum, nil
+}
+
// Product gets the product of the values in a collection. If collection is empty 1 is returned.
// Play: https://go.dev/play/p/2_kjM_smtAH
func Product[T constraints.Float | constraints.Integer | constraints.Complex](collection []T) T {
- if collection == nil {
- return 1
- }
-
- if len(collection) == 0 {
- return 1
- }
-
var product T = 1
for i := range collection {
product *= collection[i]
@@ -106,14 +125,6 @@ func Product[T constraints.Float | constraints.Integer | constraints.Complex](co
// ProductBy summarizes the values in a collection using the given return value from the iteration function. If collection is empty 1 is returned.
// Play: https://go.dev/play/p/wadzrWr9Aer
func ProductBy[T any, R constraints.Float | constraints.Integer | constraints.Complex](collection []T, iteratee func(item T) R) R {
- if collection == nil {
- return 1
- }
-
- if len(collection) == 0 {
- return 1
- }
-
var product R = 1
for i := range collection {
product *= iteratee(collection[i])
@@ -121,6 +132,21 @@ func ProductBy[T any, R constraints.Float | constraints.Integer | constraints.Co
return product
}
+// ProductByErr summarizes the values in a collection using the given return value from the iteration function.
+// If the iteratee returns an error, iteration stops and the error is returned.
+// If collection is empty 1 and nil error are returned.
+func ProductByErr[T any, R constraints.Float | constraints.Integer | constraints.Complex](collection []T, iteratee func(item T) (R, error)) (R, error) {
+ var product R = 1
+ for i := range collection {
+ v, err := iteratee(collection[i])
+ if err != nil {
+ return product, err
+ }
+ product *= v
+ }
+ return product, nil
+}
+
// Mean calculates the mean of a collection of numbers.
// Play: https://go.dev/play/p/tPURSuteUsP
func Mean[T constraints.Float | constraints.Integer](collection []T) T {
@@ -143,6 +169,21 @@ func MeanBy[T any, R constraints.Float | constraints.Integer](collection []T, it
return sum / length
}
+// MeanByErr calculates the mean of a collection of numbers using the given return value from the iteration function.
+// If the iteratee returns an error, iteration stops and the error is returned.
+// If collection is empty 0 and nil error are returned.
+func MeanByErr[T any, R constraints.Float | constraints.Integer](collection []T, iteratee func(item T) (R, error)) (R, error) {
+ length := R(len(collection))
+ if length == 0 {
+ return 0, nil
+ }
+ sum, err := SumByErr(collection, iteratee)
+ if err != nil {
+ return 0, err
+ }
+ return sum / length, nil
+}
+
// Mode returns the mode (most frequent value) of a collection.
// If multiple values have the same highest frequency, then multiple values are returned.
// If the collection is empty, then the zero value of T is returned.
diff --git a/vendor/github.com/samber/lo/mutable/slice.go b/vendor/github.com/samber/lo/mutable/slice.go
index f3412c1..4b8e916 100644
--- a/vendor/github.com/samber/lo/mutable/slice.go
+++ b/vendor/github.com/samber/lo/mutable/slice.go
@@ -10,9 +10,9 @@ import "github.com/samber/lo/internal/xrand"
// Play: https://go.dev/play/p/0jY3Z0B7O_5
func Filter[T any, Slice ~[]T](collection Slice, predicate func(item T) bool) Slice {
j := 0
- for _, item := range collection {
- if predicate(item) {
- collection[j] = item
+ for i := range collection {
+ if predicate(collection[i]) {
+ collection[j] = collection[i]
j++
}
}
@@ -26,9 +26,9 @@ func Filter[T any, Slice ~[]T](collection Slice, predicate func(item T) bool) Sl
// Note that the order of elements in the original slice is preserved in the output.
func FilterI[T any, Slice ~[]T](collection Slice, predicate func(item T, index int) bool) Slice {
j := 0
- for i, item := range collection {
- if predicate(item, i) {
- collection[j] = item
+ for i := range collection {
+ if predicate(collection[i], i) {
+ collection[j] = collection[i]
j++
}
}
@@ -38,17 +38,17 @@ func FilterI[T any, Slice ~[]T](collection Slice, predicate func(item T, index i
// Map is a generic function that modifies the input slice in-place to contain the result of applying the provided
// function to each element of the slice. The function returns the modified slice, which has the same length as the original.
// Play: https://go.dev/play/p/0jY3Z0B7O_5
-func Map[T any, Slice ~[]T](collection Slice, fn func(item T) T) {
+func Map[T any, Slice ~[]T](collection Slice, transform func(item T) T) {
for i := range collection {
- collection[i] = fn(collection[i])
+ collection[i] = transform(collection[i])
}
}
// MapI is a generic function that modifies the input slice in-place to contain the result of applying the provided
// function to each element of the slice. The function returns the modified slice, which has the same length as the original.
-func MapI[T any, Slice ~[]T](collection Slice, fn func(item T, index int) T) {
+func MapI[T any, Slice ~[]T](collection Slice, transform func(item T, index int) T) {
for i := range collection {
- collection[i] = fn(collection[i], i)
+ collection[i] = transform(collection[i], i)
}
}
diff --git a/vendor/github.com/samber/lo/retry.go b/vendor/github.com/samber/lo/retry.go
index e8b98c4..a4779f6 100644
--- a/vendor/github.com/samber/lo/retry.go
+++ b/vendor/github.com/samber/lo/retry.go
@@ -315,10 +315,6 @@ func (th *throttleBy[T]) throttledFunc(key T) {
th.mu.Lock()
defer th.mu.Unlock()
- if _, ok := th.count[key]; !ok {
- th.count[key] = 0
- }
-
if th.count[key] < th.countLimit {
th.count[key]++
@@ -361,7 +357,7 @@ func NewThrottleWithCount(interval time.Duration, count int, f ...func()) (throt
}
})
- throttleFn, reset := NewThrottleByWithCount[struct{}](interval, count, callbacks...)
+ throttleFn, reset := NewThrottleByWithCount(interval, count, callbacks...)
return func() {
throttleFn(struct{}{})
}, reset
@@ -371,7 +367,7 @@ func NewThrottleWithCount(interval time.Duration, count int, f ...func()) (throt
// This returns 2 functions, First one is throttled function and Second one is a function to reset interval.
// Play: https://go.dev/play/p/0Wv6oX7dHdC
func NewThrottleBy[T comparable](interval time.Duration, f ...func(key T)) (throttle func(key T), reset func()) {
- return NewThrottleByWithCount[T](interval, 1, f...)
+ return NewThrottleByWithCount(interval, 1, f...)
}
// NewThrottleByWithCount is NewThrottleBy with count limit, throttled function will be invoked count times in every interval.
diff --git a/vendor/github.com/samber/lo/slice.go b/vendor/github.com/samber/lo/slice.go
index d35d276..a53da54 100644
--- a/vendor/github.com/samber/lo/slice.go
+++ b/vendor/github.com/samber/lo/slice.go
@@ -21,32 +21,66 @@ func Filter[T any, Slice ~[]T](collection Slice, predicate func(item T, index in
return result
}
+// FilterErr iterates over elements of collection, returning a slice of all elements predicate returns true for.
+// If the predicate returns an error, iteration stops immediately and returns the error.
+// Play: https://go.dev/play/p/Apjg3WeSi7K
+func FilterErr[T any, Slice ~[]T](collection Slice, predicate func(item T, index int) (bool, error)) (Slice, error) {
+ result := make(Slice, 0, len(collection))
+
+ for i := range collection {
+ ok, err := predicate(collection[i], i)
+ if err != nil {
+ return nil, err
+ }
+ if ok {
+ result = append(result, collection[i])
+ }
+ }
+
+ return result, nil
+}
+
// Map manipulates a slice and transforms it to a slice of another type.
// Play: https://go.dev/play/p/OkPcYAhBo0D
-func Map[T, R any](collection []T, iteratee func(item T, index int) R) []R {
+func Map[T, R any](collection []T, transform func(item T, index int) R) []R {
result := make([]R, len(collection))
for i := range collection {
- result[i] = iteratee(collection[i], i)
+ result[i] = transform(collection[i], i)
}
return result
}
+// MapErr manipulates a slice and transforms it to a slice of another type.
+// It returns the first error returned by the transform function.
+func MapErr[T, R any](collection []T, transform func(item T, index int) (R, error)) ([]R, error) {
+ result := make([]R, len(collection))
+
+ for i := range collection {
+ r, err := transform(collection[i], i)
+ if err != nil {
+ return nil, err
+ }
+ result[i] = r
+ }
+
+ return result, nil
+}
+
// UniqMap manipulates a slice and transforms it to a slice of another type with unique values.
// Play: https://go.dev/play/p/fygzLBhvUdB
-func UniqMap[T any, R comparable](collection []T, iteratee func(item T, index int) R) []R {
- result := make([]R, 0, len(collection))
+func UniqMap[T any, R comparable](collection []T, transform func(item T, index int) R) []R {
seen := make(map[R]struct{}, len(collection))
for i := range collection {
- r := iteratee(collection[i], i)
+ r := transform(collection[i], i)
if _, ok := seen[r]; !ok {
- result = append(result, r)
seen[r] = struct{}{}
}
}
- return result
+
+ return Keys(seen)
}
// FilterMap returns a slice obtained after both filtering and mapping using the given callback function.
@@ -71,16 +105,34 @@ func FilterMap[T, R any](collection []T, callback func(item T, index int) (R, bo
// The transform function can either return a slice or a `nil`, and in the `nil` case
// no value is added to the final slice.
// Play: https://go.dev/play/p/pFCF5WVB225
-func FlatMap[T, R any](collection []T, iteratee func(item T, index int) []R) []R {
+func FlatMap[T, R any](collection []T, transform func(item T, index int) []R) []R {
result := make([]R, 0, len(collection))
for i := range collection {
- result = append(result, iteratee(collection[i], i)...)
+ result = append(result, transform(collection[i], i)...)
}
return result
}
+// FlatMapErr manipulates a slice and transforms and flattens it to a slice of another type.
+// The transform function can either return a slice or a `nil`, and in the `nil` case
+// no value is added to the final slice.
+// It returns the first error returned by the transform function.
+func FlatMapErr[T, R any](collection []T, transform func(item T, index int) ([]R, error)) ([]R, error) {
+ result := make([]R, 0, len(collection))
+
+ for i := range collection {
+ r, err := transform(collection[i], i)
+ if err != nil {
+ return nil, err
+ }
+ result = append(result, r...)
+ }
+
+ return result, nil
+}
+
// Reduce reduces collection to a value which is the accumulated result of running each element in collection
// through accumulator, where each successive invocation is supplied the return value of the previous.
// Play: https://go.dev/play/p/CgHYNUpOd1I
@@ -92,6 +144,22 @@ func Reduce[T, R any](collection []T, accumulator func(agg R, item T, index int)
return initial
}
+// ReduceErr reduces collection to a value which is the accumulated result of running each element in collection
+// through accumulator, where each successive invocation is supplied the return value of the previous.
+// It returns the first error returned by the accumulator function.
+func ReduceErr[T, R any](collection []T, accumulator func(agg R, item T, index int) (R, error), initial R) (R, error) {
+ for i := range collection {
+ result, err := accumulator(initial, collection[i], i)
+ if err != nil {
+ var zero R
+ return zero, err
+ }
+ initial = result
+ }
+
+ return initial, nil
+}
+
// ReduceRight is like Reduce except that it iterates over elements of collection from right to left.
// Play: https://go.dev/play/p/Fq3W70l7wXF
func ReduceRight[T, R any](collection []T, accumulator func(agg R, item T, index int) R, initial R) R {
@@ -102,20 +170,35 @@ func ReduceRight[T, R any](collection []T, accumulator func(agg R, item T, index
return initial
}
-// ForEach iterates over elements of collection and invokes iteratee for each element.
+// ReduceRightErr is like ReduceRight except that the accumulator function can return an error.
+// It returns the first error returned by the accumulator function.
+func ReduceRightErr[T, R any](collection []T, accumulator func(agg R, item T, index int) (R, error), initial R) (R, error) {
+ for i := len(collection) - 1; i >= 0; i-- {
+ result, err := accumulator(initial, collection[i], i)
+ if err != nil {
+ var zero R
+ return zero, err
+ }
+ initial = result
+ }
+
+ return initial, nil
+}
+
+// ForEach iterates over elements of collection and invokes callback for each element.
// Play: https://go.dev/play/p/oofyiUPRf8t
-func ForEach[T any](collection []T, iteratee func(item T, index int)) {
+func ForEach[T any](collection []T, callback func(item T, index int)) {
for i := range collection {
- iteratee(collection[i], i)
+ callback(collection[i], i)
}
}
-// ForEachWhile iterates over elements of collection and invokes iteratee for each element
+// ForEachWhile iterates over elements of collection and invokes predicate for each element
// collection return value decide to continue or break, like do while().
// Play: https://go.dev/play/p/QnLGt35tnow
-func ForEachWhile[T any](collection []T, iteratee func(item T, index int) bool) {
+func ForEachWhile[T any](collection []T, predicate func(item T, index int) bool) {
for i := range collection {
- if !iteratee(collection[i], i) {
+ if !predicate(collection[i], i) {
break
}
}
@@ -175,6 +258,31 @@ func UniqBy[T any, U comparable, Slice ~[]T](collection Slice, iteratee func(ite
return result
}
+// UniqByErr returns a duplicate-free version of a slice, in which only the first occurrence of each element is kept.
+// The order of result values is determined by the order they occur in the slice. It accepts `iteratee` which is
+// invoked for each element in the slice to generate the criterion by which uniqueness is computed.
+// It returns the first error returned by the iteratee function.
+func UniqByErr[T any, U comparable, Slice ~[]T](collection Slice, iteratee func(item T) (U, error)) (Slice, error) {
+ result := make(Slice, 0, len(collection))
+ seen := make(map[U]struct{}, len(collection))
+
+ for i := range collection {
+ key, err := iteratee(collection[i])
+ if err != nil {
+ return nil, err
+ }
+
+ if _, ok := seen[key]; ok {
+ continue
+ }
+
+ seen[key] = struct{}{}
+ result = append(result, collection[i])
+ }
+
+ return result, nil
+}
+
// GroupBy returns an object composed of keys generated from the results of running each element of collection through iteratee.
// Play: https://go.dev/play/p/XnQBd_v6brd
func GroupBy[T any, U comparable, Slice ~[]T](collection Slice, iteratee func(item T) U) map[U]Slice {
@@ -189,13 +297,30 @@ func GroupBy[T any, U comparable, Slice ~[]T](collection Slice, iteratee func(it
return result
}
-// GroupByMap returns an object composed of keys generated from the results of running each element of collection through iteratee.
+// GroupByErr returns an object composed of keys generated from the results of running each element of collection through iteratee.
+// It returns the first error returned by the iteratee function.
+func GroupByErr[T any, U comparable, Slice ~[]T](collection Slice, iteratee func(item T) (U, error)) (map[U]Slice, error) {
+ result := map[U]Slice{}
+
+ for i := range collection {
+ key, err := iteratee(collection[i])
+ if err != nil {
+ return nil, err
+ }
+
+ result[key] = append(result[key], collection[i])
+ }
+
+ return result, nil
+}
+
+// GroupByMap returns an object composed of keys generated from the results of running each element of collection through transform.
// Play: https://go.dev/play/p/iMeruQ3_W80
-func GroupByMap[T any, K comparable, V any](collection []T, iteratee func(item T) (K, V)) map[K][]V {
+func GroupByMap[T any, K comparable, V any](collection []T, transform func(item T) (K, V)) map[K][]V {
result := map[K][]V{}
for i := range collection {
- k, v := iteratee(collection[i])
+ k, v := transform(collection[i])
result[k] = append(result[k], v)
}
@@ -203,6 +328,23 @@ func GroupByMap[T any, K comparable, V any](collection []T, iteratee func(item T
return result
}
+// GroupByMapErr returns an object composed of keys generated from the results of running each element of collection through transform.
+// It returns the first error returned by the transform function.
+func GroupByMapErr[T any, K comparable, V any](collection []T, transform func(item T) (K, V, error)) (map[K][]V, error) {
+ result := map[K][]V{}
+
+ for i := range collection {
+ k, v, err := transform(collection[i])
+ if err != nil {
+ return nil, err
+ }
+
+ result[k] = append(result[k], v)
+ }
+
+ return result, nil
+}
+
// Chunk returns a slice of elements split into groups of length size. If the slice can't be split evenly,
// the final chunk will be the remaining elements.
// Play: https://go.dev/play/p/kEMkFbdu85g
@@ -245,13 +387,12 @@ func PartitionBy[T any, K comparable, Slice ~[]T](collection Slice, iteratee fun
key := iteratee(collection[i])
resultIndex, ok := seen[key]
- if !ok {
- resultIndex = len(result)
- seen[key] = resultIndex
- result = append(result, Slice{})
+ if ok {
+ result[resultIndex] = append(result[resultIndex], collection[i])
+ } else {
+ seen[key] = len(result)
+ result = append(result, Slice{collection[i]})
}
-
- result[resultIndex] = append(result[resultIndex], collection[i])
}
return result
@@ -261,7 +402,34 @@ func PartitionBy[T any, K comparable, Slice ~[]T](collection Slice, iteratee fun
// return Values[K, []T](groups)
}
+// PartitionByErr partitions a slice into groups determined by a key computed from each element.
+// The order of the partitions is determined by the order they occur in collection. The grouping
+// is generated from the results of running each element of collection through iteratee.
+// It returns the first error returned by the iteratee function.
+func PartitionByErr[T any, K comparable, Slice ~[]T](collection Slice, iteratee func(item T) (K, error)) ([]Slice, error) {
+ result := []Slice{}
+ seen := map[K]int{}
+
+ for i := range collection {
+ key, err := iteratee(collection[i])
+ if err != nil {
+ return nil, err
+ }
+
+ resultIndex, ok := seen[key]
+ if ok {
+ result[resultIndex] = append(result[resultIndex], collection[i])
+ } else {
+ seen[key] = len(result)
+ result = append(result, Slice{collection[i]})
+ }
+ }
+
+ return result, nil
+}
+
// Flatten returns a slice a single level deep.
+// See also: Concat
// Play: https://go.dev/play/p/rbp9ORaMpjw
func Flatten[T any, Slice ~[]T](collection []Slice) Slice {
totalLen := 0
@@ -277,6 +445,50 @@ func Flatten[T any, Slice ~[]T](collection []Slice) Slice {
return result
}
+// Concat returns a new slice containing all the elements in collections. Concat conserves the order of the elements.
+// See also: Flatten, Union.
+func Concat[T any, Slice ~[]T](collections ...Slice) Slice {
+ return Flatten(collections)
+}
+
+// Window creates a slice of sliding windows of a given size.
+// Each window overlaps with the previous one by size-1 elements.
+// This is equivalent to Sliding(collection, size, 1).
+func Window[T any, Slice ~[]T](collection Slice, size int) []Slice {
+ if size <= 0 {
+ panic("lo.Window: size must be greater than 0")
+ }
+ return Sliding(collection, size, 1)
+}
+
+// Sliding creates a slice of sliding windows of a given size with a given step.
+// If step is equal to size, windows don't overlap (similar to Chunk).
+// If step is less than size, windows overlap.
+func Sliding[T any, Slice ~[]T](collection Slice, size, step int) []Slice {
+ if size <= 0 {
+ panic("lo.Sliding: size must be greater than 0")
+ }
+
+ if step <= 0 {
+ panic("lo.Sliding: step must be greater than 0")
+ }
+
+ n := len(collection) - size
+ if n < 0 {
+ return []Slice{}
+ }
+
+ result := make([]Slice, 0, n/step+1)
+
+ for i := 0; i <= n; i += step {
+ window := make(Slice, size)
+ copy(window, collection[i:i+size])
+ result = append(result, window)
+ }
+
+ return result
+}
+
// Interleave round-robin alternating input slices and sequentially appending value at index into result.
// Play: https://go.dev/play/p/-RJkTLQEDVt
func Interleave[T any, Slice ~[]T](collections ...Slice) Slice {
@@ -359,16 +571,32 @@ func Repeat[T Clonable[T]](count int, initial T) []T {
// RepeatBy builds a slice with values returned by N calls of callback.
// Play: https://go.dev/play/p/ozZLCtX_hNU
-func RepeatBy[T any](count int, predicate func(index int) T) []T {
+func RepeatBy[T any](count int, callback func(index int) T) []T {
result := make([]T, 0, count)
for i := 0; i < count; i++ {
- result = append(result, predicate(i))
+ result = append(result, callback(i))
}
return result
}
+// RepeatByErr builds a slice with values returned by N calls of callback.
+// It returns the first error returned by the callback function.
+func RepeatByErr[T any](count int, callback func(index int) (T, error)) ([]T, error) {
+ result := make([]T, 0, count)
+
+ for i := 0; i < count; i++ {
+ r, err := callback(i)
+ if err != nil {
+ return nil, err
+ }
+ result = append(result, r)
+ }
+
+ return result, nil
+}
+
// KeyBy transforms a slice or a slice of structs to a map based on a pivot callback.
// Play: https://go.dev/play/p/ccUiUL_Lnel
func KeyBy[K comparable, V any](collection []V, iteratee func(item V) K) map[K]V {
@@ -382,6 +610,23 @@ func KeyBy[K comparable, V any](collection []V, iteratee func(item V) K) map[K]V
return result
}
+// KeyByErr transforms a slice or a slice of structs to a map based on a pivot callback to compute keys.
+// Iteratee can return an error to stop iteration immediately.
+// Play: https://go.dev/play/p/ccUiUL_Lnel
+func KeyByErr[K comparable, V any](collection []V, iteratee func(item V) (K, error)) (map[K]V, error) {
+ result := make(map[K]V, len(collection))
+
+ for i := range collection {
+ k, err := iteratee(collection[i])
+ if err != nil {
+ return nil, err
+ }
+ result[k] = collection[i]
+ }
+
+ return result, nil
+}
+
// Associate returns a map containing key-value pairs provided by transform function applied to elements of the given slice.
// If any of two pairs have the same key the last one gets added to the map.
// The order of keys in returned map is not specified and is not guaranteed to be the same from the original slice.
@@ -525,33 +770,108 @@ func DropRightWhile[T any, Slice ~[]T](collection Slice, predicate func(item T)
return append(result, collection[:i+1]...)
}
+// Take takes the first n elements from a slice.
+func Take[T any, Slice ~[]T](collection Slice, n int) Slice {
+ if n < 0 {
+ panic("lo.Take: n must not be negative")
+ }
+
+ if n == 0 {
+ return make(Slice, 0)
+ }
+
+ size := len(collection)
+ if size == 0 {
+ return make(Slice, 0)
+ }
+
+ if n >= size {
+ result := make(Slice, size)
+ copy(result, collection)
+ return result
+ }
+
+ result := make(Slice, n)
+ copy(result, collection)
+ return result
+}
+
+// TakeWhile takes elements from the beginning of a slice while the predicate returns true.
+func TakeWhile[T any, Slice ~[]T](collection Slice, predicate func(item T) bool) Slice {
+ i := 0
+ for ; i < len(collection); i++ {
+ if !predicate(collection[i]) {
+ break
+ }
+ }
+
+ result := make(Slice, i)
+ copy(result, collection[:i])
+ return result
+}
+
// DropByIndex drops elements from a slice by the index.
// A negative index will drop elements from the end of the slice.
// Play: https://go.dev/play/p/bPIH4npZRxS
func DropByIndex[T any, Slice ~[]T](collection Slice, indexes ...int) Slice {
initialSize := len(collection)
if initialSize == 0 {
- return make(Slice, 0)
+ return Slice{}
}
- for i := range indexes {
- if indexes[i] < 0 {
- indexes[i] = initialSize + indexes[i]
+ // do not change the input
+ indexes = append(make([]int, 0, len(indexes)), indexes...)
+
+ for i, index := range indexes {
+ if index < 0 {
+ indexes[i] += initialSize
}
}
- indexes = Uniq(indexes)
sort.Ints(indexes)
- result := make(Slice, 0, initialSize)
- result = append(result, collection...)
+ prev := -1
+ indexes = mutable.Filter(indexes, func(index int) bool {
+ ok := index != prev && // uniq
+ uint(index) < uint(initialSize) // in range
- for i := range indexes {
- if indexes[i]-i < 0 || indexes[i]-i >= initialSize-i {
- continue
- }
+ prev = index
+ return ok
+ })
+
+ result := make(Slice, 0, initialSize-len(indexes))
+
+ i := 0
+ for _, index := range indexes {
+ result = append(result, collection[i:index]...)
+ i = index + 1
+ }
+
+ return append(result, collection[i:]...)
+}
+
+// TakeFilter filters elements and takes the first n elements that match the predicate.
+// Equivalent to calling Take(Filter(...)), but more efficient as it stops after finding n matches.
+func TakeFilter[T any, Slice ~[]T](collection Slice, n int, predicate func(item T, index int) bool) Slice {
+ if n < 0 {
+ panic("lo.TakeFilter: n must not be negative")
+ }
- result = append(result[:indexes[i]-i], result[indexes[i]-i+1:]...)
+ if n == 0 {
+ return make(Slice, 0)
+ }
+
+ result := make(Slice, 0, n)
+ count := 0
+
+ for i := range collection {
+ if predicate(collection[i], i) {
+ result = append(result, collection[i])
+ count++
+ if count >= n {
+ break
+ }
+ }
}
return result
@@ -571,6 +891,25 @@ func Reject[T any, Slice ~[]T](collection Slice, predicate func(item T, index in
return result
}
+// RejectErr is the opposite of FilterErr, this method returns the elements of collection that predicate does not return true for.
+// If the predicate returns an error, iteration stops immediately and returns the error.
+// Play: https://go.dev/play/p/pFCF5WVB225
+func RejectErr[T any, Slice ~[]T](collection Slice, predicate func(item T, index int) (bool, error)) (Slice, error) {
+ result := Slice{}
+
+ for i := range collection {
+ match, err := predicate(collection[i], i)
+ if err != nil {
+ return nil, err
+ }
+ if !match {
+ result = append(result, collection[i])
+ }
+ }
+
+ return result, nil
+}
+
// RejectMap is the opposite of FilterMap, this method returns a slice obtained after both filtering and mapping using the given callback function.
// The callback function should return two values:
// - the result of the mapping operation and
@@ -635,6 +974,24 @@ func CountBy[T any](collection []T, predicate func(item T) bool) int {
return count
}
+// CountByErr counts the number of elements in the collection for which predicate is true.
+// It returns the first error returned by the predicate.
+func CountByErr[T any](collection []T, predicate func(item T) (bool, error)) (int, error) {
+ var count int
+
+ for i := range collection {
+ ok, err := predicate(collection[i])
+ if err != nil {
+ return 0, err
+ }
+ if ok {
+ count++
+ }
+ }
+
+ return count, nil
+}
+
// CountValues counts the number of each element in the collection.
// Play: https://go.dev/play/p/-p-PyLT4dfy
func CountValues[T comparable](collection []T) map[T]int {
@@ -647,14 +1004,14 @@ func CountValues[T comparable](collection []T) map[T]int {
return result
}
-// CountValuesBy counts the number of each element returned from mapper function.
+// CountValuesBy counts the number of each element returned from transform function.
// Is equivalent to chaining lo.Map and lo.CountValues.
// Play: https://go.dev/play/p/2U0dG1SnOmS
-func CountValuesBy[T any, U comparable](collection []T, mapper func(item T) U) map[U]int {
+func CountValuesBy[T any, U comparable](collection []T, transform func(item T) U) map[U]int {
result := make(map[U]int)
for i := range collection {
- result[mapper(collection[i])]++
+ result[transform(collection[i])]++
}
return result
@@ -683,27 +1040,24 @@ func Subset[T any, Slice ~[]T](collection Slice, offset int, length uint) Slice
return collection[offset : offset+int(length)]
}
-// Slice returns a copy of a slice from `start` up to, but not including `end`. Like `slice[start:end]`, but does not panic on overflow.
+// Slice returns a slice from `start` up to, but not including `end`. Like `slice[start:end]`, but does not panic on overflow.
// Play: https://go.dev/play/p/8XWYhfMMA1h
func Slice[T any, Slice ~[]T](collection Slice, start, end int) Slice {
- size := len(collection)
-
if start >= end {
return Slice{}
}
- if start > size {
- start = size
- }
+ size := len(collection)
if start < 0 {
start = 0
+ } else if start > size {
+ start = size
}
- if end > size {
- end = size
- }
if end < 0 {
end = 0
+ } else if end > size {
+ end = size
}
return collection[start:end]
@@ -731,6 +1085,20 @@ func ReplaceAll[T comparable, Slice ~[]T](collection Slice, old, nEw T) Slice {
return Replace(collection, old, nEw, -1)
}
+// Clone returns a shallow copy of the collection.
+func Clone[T any, Slice ~[]T](collection Slice) Slice {
+ // backporting from slices.Clone in Go 1.21
+ // when we drop support for Go 1.20, this can be replaced with: return slices.Clone(collection)
+
+ // Preserve nilness in case it matters.
+ if collection == nil {
+ return nil
+ }
+ // Avoid s[:0:0] as it leads to unwanted liveness when cloning a
+ // zero-length slice of a large array; see https://go.dev/issue/68488.
+ return append(Slice{}, collection...)
+}
+
// Compact returns a slice of all non-zero elements.
// Play: https://go.dev/play/p/tXiy-iK6PAc
func Compact[T comparable, Slice ~[]T](collection Slice) Slice {
@@ -759,8 +1127,8 @@ func IsSorted[T constraints.Ordered](collection []T) bool {
return true
}
-// IsSortedByKey checks if a slice is sorted by iteratee.
-func IsSortedByKey[T any, K constraints.Ordered](collection []T, iteratee func(item T) K) bool {
+// IsSortedBy checks if a slice is sorted by iteratee.
+func IsSortedBy[T any, K constraints.Ordered](collection []T, iteratee func(item T) K) bool {
size := len(collection)
for i := 0; i < size-1; i++ {
@@ -772,6 +1140,13 @@ func IsSortedByKey[T any, K constraints.Ordered](collection []T, iteratee func(i
return true
}
+// IsSortedByKey checks if a slice is sorted by iteratee.
+//
+// Deprecated: Use lo.IsSortedBy instead.
+func IsSortedByKey[T any, K constraints.Ordered](collection []T, iteratee func(item T) K) bool {
+ return IsSortedBy(collection, iteratee)
+}
+
// Splice inserts multiple elements at index i. A negative index counts back
// from the end of the slice. The helper is protected against overflow errors.
// Play: https://go.dev/play/p/G5_GhkeSUBA
@@ -828,20 +1203,10 @@ func Cut[T comparable, Slice ~[]T](collection, separator Slice) (before, after S
// If prefix is the empty []T, CutPrefix returns collection, true.
// Play: https://go.dev/play/p/7Plak4a1ICl
func CutPrefix[T comparable, Slice ~[]T](collection, separator Slice) (after Slice, found bool) {
- if len(separator) == 0 {
- return collection, true
+ if HasPrefix(collection, separator) {
+ return collection[len(separator):], true
}
- if len(separator) > len(collection) {
- return collection, false
- }
-
- for i := range separator {
- if collection[i] != separator[i] {
- return collection, false
- }
- }
-
- return collection[len(separator):], true
+ return collection, false
}
// CutSuffix returns collection without the provided ending suffix []T and reports
@@ -849,21 +1214,10 @@ func CutPrefix[T comparable, Slice ~[]T](collection, separator Slice) (after Sli
// If suffix is the empty []T, CutSuffix returns collection, true.
// Play: https://go.dev/play/p/7FKfBFvPTaT
func CutSuffix[T comparable, Slice ~[]T](collection, separator Slice) (before Slice, found bool) {
- if len(separator) == 0 {
- return collection, true
- }
- if len(separator) > len(collection) {
- return collection, false
- }
-
- start := len(collection) - len(separator)
- for i := range separator {
- if collection[start+i] != separator[i] {
- return collection, false
- }
+ if HasSuffix(collection, separator) {
+ return collection[:len(collection)-len(separator)], true
}
-
- return collection[:start], true
+ return collection, false
}
// Trim removes all the leading and trailing cutset from the collection.
@@ -911,12 +1265,11 @@ func TrimPrefix[T comparable, Slice ~[]T](collection, prefix Slice) Slice {
return collection
}
- for {
- if !HasPrefix(collection, prefix) {
- return collection
- }
+ for HasPrefix(collection, prefix) {
collection = collection[len(prefix):]
}
+
+ return collection
}
// TrimRight removes all the trailing cutset from the collection.
@@ -937,10 +1290,9 @@ func TrimSuffix[T comparable, Slice ~[]T](collection, suffix Slice) Slice {
return collection
}
- for {
- if !HasSuffix(collection, suffix) {
- return collection
- }
+ for HasSuffix(collection, suffix) {
collection = collection[:len(collection)-len(suffix)]
}
+
+ return collection
}
diff --git a/vendor/github.com/samber/lo/string.go b/vendor/github.com/samber/lo/string.go
index 9590114..9b0fc6e 100644
--- a/vendor/github.com/samber/lo/string.go
+++ b/vendor/github.com/samber/lo/string.go
@@ -7,10 +7,10 @@ import (
"unicode"
"unicode/utf8"
- "github.com/samber/lo/internal/xrand"
-
"golang.org/x/text/cases"
"golang.org/x/text/language"
+
+ "github.com/samber/lo/internal/xrand"
)
var (
@@ -41,7 +41,7 @@ func RandomString(size int, charset []rune) string {
}
// see https://stackoverflow.com/questions/22892120/how-to-generate-a-random-string-of-a-fixed-length-in-go
- sb := strings.Builder{}
+ var sb strings.Builder
sb.Grow(size)
if len(charset) == 1 {
@@ -100,42 +100,107 @@ func nearestPowerOfTwo(capacity int) int {
return n + 1
}
-// Substring return part of a string.
+// Substring extracts a substring from a string with Unicode character (rune) awareness.
+// offset - starting position of the substring (can be positive, negative, or zero)
+// length - number of characters to extract
+// With positive offset, counting starts from the beginning of the string
+// With negative offset, counting starts from the end of the string
// Play: https://go.dev/play/p/TQlxQi82Lu1
func Substring[T ~string](str T, offset int, length uint) T {
- rs := []rune(str)
- size := len(rs)
+ str = substring(str, offset, length)
- if offset < 0 {
- offset = size + offset
- if offset < 0 {
- offset = 0
- }
+ // Validate UTF-8 and fix invalid sequences
+ if !utf8.ValidString(string(str)) {
+ // Convert to []rune to replicate behavior with duplicated �
+ str = T([]rune(str))
}
- if offset >= size {
- return Empty[T]()
- }
+ // Remove null bytes from result
+ return T(strings.ReplaceAll(string(str), "\x00", ""))
+}
- if length > uint(size)-uint(offset) {
- length = uint(size - offset)
- }
+func substring[T ~string](str T, offset int, length uint) T {
+ switch {
+ // Empty length or offset beyond string bounds - return empty string
+ case length == 0, offset >= len(str):
+ return ""
+
+ // Positive offset - count from the beginning
+ case offset > 0:
+ // Skip offset runes from the start
+ for i, r := range str {
+ if offset--; offset == 0 {
+ str = str[i+utf8.RuneLen(r):]
+ break
+ }
+ }
+
+ // If couldn't skip enough runes - string is shorter than offset
+ if offset != 0 {
+ return ""
+ }
+
+ // If remaining string is shorter than or equal to length - return it entirely
+ if uint(len(str)) <= length {
+ return str
+ }
+
+ // Otherwise proceed to trimming by length
+ fallthrough
+
+ // Zero offset or offset less than minus string length - start from beginning
+ case offset < -len(str), offset == 0:
+ // Count length runes from the start
+ for i := range str {
+ if length == 0 {
+ return str[:i]
+ }
+ length--
+ }
+
+ return str
- return T(strings.ReplaceAll(string(rs[offset:offset+int(length)]), "\x00", ""))
+ // Negative offset - count from the end of string
+ default: // -len(str) < offset < 0
+ // Helper function to move backward through runes
+ backwardPos := func(end int, count uint) (start int) {
+ for {
+ _, i := utf8.DecodeLastRuneInString(string(str[:end]))
+ end -= i
+
+ if count--; count == 0 || end == 0 {
+ return end
+ }
+ }
+ }
+
+ offset := uint(-offset)
+
+ // If offset is less than or equal to length - take from position to end
+ if offset <= length {
+ start := backwardPos(len(str), offset)
+ return str[start:]
+ }
+
+ // Otherwise calculate start and end positions
+ end := backwardPos(len(str), offset-length)
+ start := backwardPos(end, length)
+
+ return str[start:end]
+ }
}
// ChunkString returns a slice of strings split into groups of length size. If the string can't be split evenly,
// the final chunk will be the remaining characters.
// Play: https://go.dev/play/p/__FLTuJVz54
+//
+// Note: lo.ChunkString and lo.Chunk functions behave inconsistently for empty input: lo.ChunkString("", n) returns [""] instead of [].
+// See https://github.com/samber/lo/issues/788
func ChunkString[T ~string](str T, size int) []T {
if size <= 0 {
panic("lo.ChunkString: size must be greater than 0")
}
- if len(str) == 0 {
- return []T{""}
- }
-
if size >= len(str) {
return []T{str}
}
@@ -212,6 +277,7 @@ func Words(str string) []string {
// example: Int8Value => Int 8Value => Int 8 Value
str = splitNumberLetterReg.ReplaceAllString(str, "$1 $2")
var result strings.Builder
+ result.Grow(len(str))
for _, r := range str {
if unicode.IsLetter(r) || unicode.IsDigit(r) {
result.WriteRune(r)
@@ -228,26 +294,25 @@ func Capitalize(str string) string {
return cases.Title(language.English).String(str)
}
-// Ellipsis trims and truncates a string to a specified length **in bytes** and appends an ellipsis
-// if truncated. If the string contains non-ASCII characters (which may occupy multiple bytes in UTF-8),
-// truncating by byte length may split a character in the middle, potentially resulting in garbled output.
+// Ellipsis trims and truncates a string to a specified length in runes and appends an ellipsis
+// if truncated. The length parameter counts Unicode code points (runes), not bytes, so multi-byte
+// characters such as emoji or CJK ideographs are never split in the middle.
// Play: https://go.dev/play/p/qE93rgqe1TW
func Ellipsis(str string, length int) string {
str = strings.TrimSpace(str)
- if len(str) > length {
- if len(str) < 3 || length < 3 {
- return "..."
+ const ellipsis = "..."
+
+ cutPosition := 0
+ for i := range str {
+ if length == len(ellipsis) {
+ cutPosition = i
+ }
+
+ if length--; length < 0 {
+ return strings.TrimSpace(str[:cutPosition]) + ellipsis
}
- return strings.TrimSpace(str[0:length-3]) + "..."
}
return str
}
-
-// Elipse trims and truncates a string to a specified length and appends an ellipsis if truncated.
-//
-// Deprecated: Use Ellipsis instead.
-func Elipse(str string, length int) string {
- return Ellipsis(str, length)
-}
diff --git a/vendor/github.com/samber/lo/time.go b/vendor/github.com/samber/lo/time.go
index f295adb..a2b1c7e 100644
--- a/vendor/github.com/samber/lo/time.go
+++ b/vendor/github.com/samber/lo/time.go
@@ -6,94 +6,94 @@ import (
// Duration returns the time taken to execute a function.
// Play: https://go.dev/play/p/HQfbBbAXaFP
-func Duration(cb func()) time.Duration {
- return Duration0(cb)
+func Duration(callback func()) time.Duration {
+ return Duration0(callback)
}
// Duration0 returns the time taken to execute a function.
// Play: https://go.dev/play/p/HQfbBbAXaFP
-func Duration0(cb func()) time.Duration {
+func Duration0(callback func()) time.Duration {
start := time.Now()
- cb()
+ callback()
return time.Since(start)
}
// Duration1 returns the time taken to execute a function.
// Play: https://go.dev/play/p/HQfbBbAXaFP
-func Duration1[A any](cb func() A) (A, time.Duration) {
+func Duration1[A any](callback func() A) (A, time.Duration) {
start := time.Now()
- a := cb()
+ a := callback()
return a, time.Since(start)
}
// Duration2 returns the time taken to execute a function.
// Play: https://go.dev/play/p/HQfbBbAXaFP
-func Duration2[A, B any](cb func() (A, B)) (A, B, time.Duration) {
+func Duration2[A, B any](callback func() (A, B)) (A, B, time.Duration) {
start := time.Now()
- a, b := cb()
+ a, b := callback()
return a, b, time.Since(start)
}
// Duration3 returns the time taken to execute a function.
// Play: https://go.dev/play/p/xr863iwkAxQ
-func Duration3[A, B, C any](cb func() (A, B, C)) (A, B, C, time.Duration) {
+func Duration3[A, B, C any](callback func() (A, B, C)) (A, B, C, time.Duration) {
start := time.Now()
- a, b, c := cb()
+ a, b, c := callback()
return a, b, c, time.Since(start)
}
// Duration4 returns the time taken to execute a function.
// Play: https://go.dev/play/p/xr863iwkAxQ
-func Duration4[A, B, C, D any](cb func() (A, B, C, D)) (A, B, C, D, time.Duration) {
+func Duration4[A, B, C, D any](callback func() (A, B, C, D)) (A, B, C, D, time.Duration) {
start := time.Now()
- a, b, c, d := cb()
+ a, b, c, d := callback()
return a, b, c, d, time.Since(start)
}
// Duration5 returns the time taken to execute a function.
// Play: https://go.dev/play/p/xr863iwkAxQ
-func Duration5[A, B, C, D, E any](cb func() (A, B, C, D, E)) (A, B, C, D, E, time.Duration) {
+func Duration5[A, B, C, D, E any](callback func() (A, B, C, D, E)) (A, B, C, D, E, time.Duration) {
start := time.Now()
- a, b, c, d, e := cb()
+ a, b, c, d, e := callback()
return a, b, c, d, e, time.Since(start)
}
// Duration6 returns the time taken to execute a function.
// Play: https://go.dev/play/p/mR4bTQKO-Tf
-func Duration6[A, B, C, D, E, F any](cb func() (A, B, C, D, E, F)) (A, B, C, D, E, F, time.Duration) {
+func Duration6[A, B, C, D, E, F any](callback func() (A, B, C, D, E, F)) (A, B, C, D, E, F, time.Duration) {
start := time.Now()
- a, b, c, d, e, f := cb()
+ a, b, c, d, e, f := callback()
return a, b, c, d, e, f, time.Since(start)
}
// Duration7 returns the time taken to execute a function.
// Play: https://go.dev/play/p/jgIAcBWWInS
-func Duration7[A, B, C, D, E, F, G any](cb func() (A, B, C, D, E, F, G)) (A, B, C, D, E, F, G, time.Duration) {
+func Duration7[A, B, C, D, E, F, G any](callback func() (A, B, C, D, E, F, G)) (A, B, C, D, E, F, G, time.Duration) {
start := time.Now()
- a, b, c, d, e, f, g := cb()
+ a, b, c, d, e, f, g := callback()
return a, b, c, d, e, f, g, time.Since(start)
}
// Duration8 returns the time taken to execute a function.
// Play: https://go.dev/play/p/T8kxpG1c5Na
-func Duration8[A, B, C, D, E, F, G, H any](cb func() (A, B, C, D, E, F, G, H)) (A, B, C, D, E, F, G, H, time.Duration) {
+func Duration8[A, B, C, D, E, F, G, H any](callback func() (A, B, C, D, E, F, G, H)) (A, B, C, D, E, F, G, H, time.Duration) {
start := time.Now()
- a, b, c, d, e, f, g, h := cb()
+ a, b, c, d, e, f, g, h := callback()
return a, b, c, d, e, f, g, h, time.Since(start)
}
// Duration9 returns the time taken to execute a function.
// Play: https://go.dev/play/p/bg9ix2VrZ0j
-func Duration9[A, B, C, D, E, F, G, H, I any](cb func() (A, B, C, D, E, F, G, H, I)) (A, B, C, D, E, F, G, H, I, time.Duration) {
+func Duration9[A, B, C, D, E, F, G, H, I any](callback func() (A, B, C, D, E, F, G, H, I)) (A, B, C, D, E, F, G, H, I, time.Duration) {
start := time.Now()
- a, b, c, d, e, f, g, h, i := cb()
+ a, b, c, d, e, f, g, h, i := callback()
return a, b, c, d, e, f, g, h, i, time.Since(start)
}
// Duration10 returns the time taken to execute a function.
// Play: https://go.dev/play/p/Y3n7oJXqJbk
-func Duration10[A, B, C, D, E, F, G, H, I, J any](cb func() (A, B, C, D, E, F, G, H, I, J)) (A, B, C, D, E, F, G, H, I, J, time.Duration) {
+func Duration10[A, B, C, D, E, F, G, H, I, J any](callback func() (A, B, C, D, E, F, G, H, I, J)) (A, B, C, D, E, F, G, H, I, J, time.Duration) {
start := time.Now()
- a, b, c, d, e, f, g, h, i, j := cb()
+ a, b, c, d, e, f, g, h, i, j := callback()
return a, b, c, d, e, f, g, h, i, j, time.Since(start)
}
diff --git a/vendor/github.com/samber/lo/tuples.go b/vendor/github.com/samber/lo/tuples.go
index d9805b2..1911f8d 100644
--- a/vendor/github.com/samber/lo/tuples.go
+++ b/vendor/github.com/samber/lo/tuples.go
@@ -101,18 +101,13 @@ func Unpack9[A, B, C, D, E, F, G, H, I any](tuple Tuple9[A, B, C, D, E, F, G, H,
// When collections are different sizes, the Tuple attributes are filled with zero value.
// Play: https://go.dev/play/p/jujaA6GaJTp
func Zip2[A, B any](a []A, b []B) []Tuple2[A, B] {
- size := Max([]int{len(a), len(b)})
+ size := uint(Max([]int{len(a), len(b)}))
- result := make([]Tuple2[A, B], 0, size)
+ result := make([]Tuple2[A, B], size)
- for index := 0; index < size; index++ {
- _a, _ := Nth(a, index)
- _b, _ := Nth(b, index)
-
- result = append(result, Tuple2[A, B]{
- A: _a,
- B: _b,
- })
+ for index := uint(0); index < size; index++ {
+ result[index].A = NthOrEmpty(a, index)
+ result[index].B = NthOrEmpty(b, index)
}
return result
@@ -123,20 +118,14 @@ func Zip2[A, B any](a []A, b []B) []Tuple2[A, B] {
// When collections are different sizes, the Tuple attributes are filled with zero value.
// Play: https://go.dev/play/p/jujaA6GaJTp
func Zip3[A, B, C any](a []A, b []B, c []C) []Tuple3[A, B, C] {
- size := Max([]int{len(a), len(b), len(c)})
-
- result := make([]Tuple3[A, B, C], 0, size)
+ size := uint(Max([]int{len(a), len(b), len(c)}))
- for index := 0; index < size; index++ {
- _a, _ := Nth(a, index)
- _b, _ := Nth(b, index)
- _c, _ := Nth(c, index)
+ result := make([]Tuple3[A, B, C], size)
- result = append(result, Tuple3[A, B, C]{
- A: _a,
- B: _b,
- C: _c,
- })
+ for index := uint(0); index < size; index++ {
+ result[index].A = NthOrEmpty(a, index)
+ result[index].B = NthOrEmpty(b, index)
+ result[index].C = NthOrEmpty(c, index)
}
return result
@@ -147,22 +136,15 @@ func Zip3[A, B, C any](a []A, b []B, c []C) []Tuple3[A, B, C] {
// When collections are different sizes, the Tuple attributes are filled with zero value.
// Play: https://go.dev/play/p/jujaA6GaJTp
func Zip4[A, B, C, D any](a []A, b []B, c []C, d []D) []Tuple4[A, B, C, D] {
- size := Max([]int{len(a), len(b), len(c), len(d)})
+ size := uint(Max([]int{len(a), len(b), len(c), len(d)}))
- result := make([]Tuple4[A, B, C, D], 0, size)
+ result := make([]Tuple4[A, B, C, D], size)
- for index := 0; index < size; index++ {
- _a, _ := Nth(a, index)
- _b, _ := Nth(b, index)
- _c, _ := Nth(c, index)
- _d, _ := Nth(d, index)
-
- result = append(result, Tuple4[A, B, C, D]{
- A: _a,
- B: _b,
- C: _c,
- D: _d,
- })
+ for index := uint(0); index < size; index++ {
+ result[index].A = NthOrEmpty(a, index)
+ result[index].B = NthOrEmpty(b, index)
+ result[index].C = NthOrEmpty(c, index)
+ result[index].D = NthOrEmpty(d, index)
}
return result
@@ -173,24 +155,16 @@ func Zip4[A, B, C, D any](a []A, b []B, c []C, d []D) []Tuple4[A, B, C, D] {
// When collections are different sizes, the Tuple attributes are filled with zero value.
// Play: https://go.dev/play/p/jujaA6GaJTp
func Zip5[A, B, C, D, E any](a []A, b []B, c []C, d []D, e []E) []Tuple5[A, B, C, D, E] {
- size := Max([]int{len(a), len(b), len(c), len(d), len(e)})
-
- result := make([]Tuple5[A, B, C, D, E], 0, size)
+ size := uint(Max([]int{len(a), len(b), len(c), len(d), len(e)}))
- for index := 0; index < size; index++ {
- _a, _ := Nth(a, index)
- _b, _ := Nth(b, index)
- _c, _ := Nth(c, index)
- _d, _ := Nth(d, index)
- _e, _ := Nth(e, index)
+ result := make([]Tuple5[A, B, C, D, E], size)
- result = append(result, Tuple5[A, B, C, D, E]{
- A: _a,
- B: _b,
- C: _c,
- D: _d,
- E: _e,
- })
+ for index := uint(0); index < size; index++ {
+ result[index].A = NthOrEmpty(a, index)
+ result[index].B = NthOrEmpty(b, index)
+ result[index].C = NthOrEmpty(c, index)
+ result[index].D = NthOrEmpty(d, index)
+ result[index].E = NthOrEmpty(e, index)
}
return result
@@ -201,26 +175,17 @@ func Zip5[A, B, C, D, E any](a []A, b []B, c []C, d []D, e []E) []Tuple5[A, B, C
// When collections are different sizes, the Tuple attributes are filled with zero value.
// Play: https://go.dev/play/p/jujaA6GaJTp
func Zip6[A, B, C, D, E, F any](a []A, b []B, c []C, d []D, e []E, f []F) []Tuple6[A, B, C, D, E, F] {
- size := Max([]int{len(a), len(b), len(c), len(d), len(e), len(f)})
+ size := uint(Max([]int{len(a), len(b), len(c), len(d), len(e), len(f)}))
- result := make([]Tuple6[A, B, C, D, E, F], 0, size)
+ result := make([]Tuple6[A, B, C, D, E, F], size)
- for index := 0; index < size; index++ {
- _a, _ := Nth(a, index)
- _b, _ := Nth(b, index)
- _c, _ := Nth(c, index)
- _d, _ := Nth(d, index)
- _e, _ := Nth(e, index)
- _f, _ := Nth(f, index)
-
- result = append(result, Tuple6[A, B, C, D, E, F]{
- A: _a,
- B: _b,
- C: _c,
- D: _d,
- E: _e,
- F: _f,
- })
+ for index := uint(0); index < size; index++ {
+ result[index].A = NthOrEmpty(a, index)
+ result[index].B = NthOrEmpty(b, index)
+ result[index].C = NthOrEmpty(c, index)
+ result[index].D = NthOrEmpty(d, index)
+ result[index].E = NthOrEmpty(e, index)
+ result[index].F = NthOrEmpty(f, index)
}
return result
@@ -231,28 +196,18 @@ func Zip6[A, B, C, D, E, F any](a []A, b []B, c []C, d []D, e []E, f []F) []Tupl
// When collections are different sizes, the Tuple attributes are filled with zero value.
// Play: https://go.dev/play/p/jujaA6GaJTp
func Zip7[A, B, C, D, E, F, G any](a []A, b []B, c []C, d []D, e []E, f []F, g []G) []Tuple7[A, B, C, D, E, F, G] {
- size := Max([]int{len(a), len(b), len(c), len(d), len(e), len(f), len(g)})
-
- result := make([]Tuple7[A, B, C, D, E, F, G], 0, size)
-
- for index := 0; index < size; index++ {
- _a, _ := Nth(a, index)
- _b, _ := Nth(b, index)
- _c, _ := Nth(c, index)
- _d, _ := Nth(d, index)
- _e, _ := Nth(e, index)
- _f, _ := Nth(f, index)
- _g, _ := Nth(g, index)
-
- result = append(result, Tuple7[A, B, C, D, E, F, G]{
- A: _a,
- B: _b,
- C: _c,
- D: _d,
- E: _e,
- F: _f,
- G: _g,
- })
+ size := uint(Max([]int{len(a), len(b), len(c), len(d), len(e), len(f), len(g)}))
+
+ result := make([]Tuple7[A, B, C, D, E, F, G], size)
+
+ for index := uint(0); index < size; index++ {
+ result[index].A = NthOrEmpty(a, index)
+ result[index].B = NthOrEmpty(b, index)
+ result[index].C = NthOrEmpty(c, index)
+ result[index].D = NthOrEmpty(d, index)
+ result[index].E = NthOrEmpty(e, index)
+ result[index].F = NthOrEmpty(f, index)
+ result[index].G = NthOrEmpty(g, index)
}
return result
@@ -263,30 +218,19 @@ func Zip7[A, B, C, D, E, F, G any](a []A, b []B, c []C, d []D, e []E, f []F, g [
// When collections are different sizes, the Tuple attributes are filled with zero value.
// Play: https://go.dev/play/p/jujaA6GaJTp
func Zip8[A, B, C, D, E, F, G, H any](a []A, b []B, c []C, d []D, e []E, f []F, g []G, h []H) []Tuple8[A, B, C, D, E, F, G, H] {
- size := Max([]int{len(a), len(b), len(c), len(d), len(e), len(f), len(g), len(h)})
-
- result := make([]Tuple8[A, B, C, D, E, F, G, H], 0, size)
-
- for index := 0; index < size; index++ {
- _a, _ := Nth(a, index)
- _b, _ := Nth(b, index)
- _c, _ := Nth(c, index)
- _d, _ := Nth(d, index)
- _e, _ := Nth(e, index)
- _f, _ := Nth(f, index)
- _g, _ := Nth(g, index)
- _h, _ := Nth(h, index)
-
- result = append(result, Tuple8[A, B, C, D, E, F, G, H]{
- A: _a,
- B: _b,
- C: _c,
- D: _d,
- E: _e,
- F: _f,
- G: _g,
- H: _h,
- })
+ size := uint(Max([]int{len(a), len(b), len(c), len(d), len(e), len(f), len(g), len(h)}))
+
+ result := make([]Tuple8[A, B, C, D, E, F, G, H], size)
+
+ for index := uint(0); index < size; index++ {
+ result[index].A = NthOrEmpty(a, index)
+ result[index].B = NthOrEmpty(b, index)
+ result[index].C = NthOrEmpty(c, index)
+ result[index].D = NthOrEmpty(d, index)
+ result[index].E = NthOrEmpty(e, index)
+ result[index].F = NthOrEmpty(f, index)
+ result[index].G = NthOrEmpty(g, index)
+ result[index].H = NthOrEmpty(h, index)
}
return result
@@ -297,32 +241,20 @@ func Zip8[A, B, C, D, E, F, G, H any](a []A, b []B, c []C, d []D, e []E, f []F,
// When collections are different sizes, the Tuple attributes are filled with zero value.
// Play: https://go.dev/play/p/jujaA6GaJTp
func Zip9[A, B, C, D, E, F, G, H, I any](a []A, b []B, c []C, d []D, e []E, f []F, g []G, h []H, i []I) []Tuple9[A, B, C, D, E, F, G, H, I] {
- size := Max([]int{len(a), len(b), len(c), len(d), len(e), len(f), len(g), len(h), len(i)})
-
- result := make([]Tuple9[A, B, C, D, E, F, G, H, I], 0, size)
-
- for index := 0; index < size; index++ {
- _a, _ := Nth(a, index)
- _b, _ := Nth(b, index)
- _c, _ := Nth(c, index)
- _d, _ := Nth(d, index)
- _e, _ := Nth(e, index)
- _f, _ := Nth(f, index)
- _g, _ := Nth(g, index)
- _h, _ := Nth(h, index)
- _i, _ := Nth(i, index)
-
- result = append(result, Tuple9[A, B, C, D, E, F, G, H, I]{
- A: _a,
- B: _b,
- C: _c,
- D: _d,
- E: _e,
- F: _f,
- G: _g,
- H: _h,
- I: _i,
- })
+ size := uint(Max([]int{len(a), len(b), len(c), len(d), len(e), len(f), len(g), len(h), len(i)}))
+
+ result := make([]Tuple9[A, B, C, D, E, F, G, H, I], size)
+
+ for index := uint(0); index < size; index++ {
+ result[index].A = NthOrEmpty(a, index)
+ result[index].B = NthOrEmpty(b, index)
+ result[index].C = NthOrEmpty(c, index)
+ result[index].D = NthOrEmpty(d, index)
+ result[index].E = NthOrEmpty(e, index)
+ result[index].F = NthOrEmpty(f, index)
+ result[index].G = NthOrEmpty(g, index)
+ result[index].H = NthOrEmpty(h, index)
+ result[index].I = NthOrEmpty(i, index)
}
return result
@@ -333,15 +265,15 @@ func Zip9[A, B, C, D, E, F, G, H, I any](a []A, b []B, c []C, d []D, e []E, f []
// When collections are different sizes, the Tuple attributes are filled with zero value.
// Play: https://go.dev/play/p/wlHur6yO8rR
func ZipBy2[A, B, Out any](a []A, b []B, iteratee func(a A, b B) Out) []Out {
- size := Max([]int{len(a), len(b)})
-
- result := make([]Out, 0, size)
+ size := uint(Max([]int{len(a), len(b)}))
- for index := 0; index < size; index++ {
- _a, _ := Nth(a, index)
- _b, _ := Nth(b, index)
+ result := make([]Out, size)
- result = append(result, iteratee(_a, _b))
+ for index := uint(0); index < size; index++ {
+ result[index] = iteratee(
+ NthOrEmpty(a, index),
+ NthOrEmpty(b, index),
+ )
}
return result
@@ -352,16 +284,16 @@ func ZipBy2[A, B, Out any](a []A, b []B, iteratee func(a A, b B) Out) []Out {
// When collections are different sizes, the Tuple attributes are filled with zero value.
// Play: https://go.dev/play/p/j9maveOnSQX
func ZipBy3[A, B, C, Out any](a []A, b []B, c []C, iteratee func(a A, b B, c C) Out) []Out {
- size := Max([]int{len(a), len(b), len(c)})
+ size := uint(Max([]int{len(a), len(b), len(c)}))
- result := make([]Out, 0, size)
-
- for index := 0; index < size; index++ {
- _a, _ := Nth(a, index)
- _b, _ := Nth(b, index)
- _c, _ := Nth(c, index)
+ result := make([]Out, size)
- result = append(result, iteratee(_a, _b, _c))
+ for index := uint(0); index < size; index++ {
+ result[index] = iteratee(
+ NthOrEmpty(a, index),
+ NthOrEmpty(b, index),
+ NthOrEmpty(c, index),
+ )
}
return result
@@ -372,17 +304,17 @@ func ZipBy3[A, B, C, Out any](a []A, b []B, c []C, iteratee func(a A, b B, c C)
// When collections are different sizes, the Tuple attributes are filled with zero value.
// Play: https://go.dev/play/p/Y1eF2Ke0Ayz
func ZipBy4[A, B, C, D, Out any](a []A, b []B, c []C, d []D, iteratee func(a A, b B, c C, d D) Out) []Out {
- size := Max([]int{len(a), len(b), len(c), len(d)})
-
- result := make([]Out, 0, size)
+ size := uint(Max([]int{len(a), len(b), len(c), len(d)}))
- for index := 0; index < size; index++ {
- _a, _ := Nth(a, index)
- _b, _ := Nth(b, index)
- _c, _ := Nth(c, index)
- _d, _ := Nth(d, index)
+ result := make([]Out, size)
- result = append(result, iteratee(_a, _b, _c, _d))
+ for index := uint(0); index < size; index++ {
+ result[index] = iteratee(
+ NthOrEmpty(a, index),
+ NthOrEmpty(b, index),
+ NthOrEmpty(c, index),
+ NthOrEmpty(d, index),
+ )
}
return result
@@ -393,18 +325,18 @@ func ZipBy4[A, B, C, D, Out any](a []A, b []B, c []C, d []D, iteratee func(a A,
// When collections are different sizes, the Tuple attributes are filled with zero value.
// Play: https://go.dev/play/p/SLynyalh5Oa
func ZipBy5[A, B, C, D, E, Out any](a []A, b []B, c []C, d []D, e []E, iteratee func(a A, b B, c C, d D, e E) Out) []Out {
- size := Max([]int{len(a), len(b), len(c), len(d), len(e)})
-
- result := make([]Out, 0, size)
-
- for index := 0; index < size; index++ {
- _a, _ := Nth(a, index)
- _b, _ := Nth(b, index)
- _c, _ := Nth(c, index)
- _d, _ := Nth(d, index)
- _e, _ := Nth(e, index)
-
- result = append(result, iteratee(_a, _b, _c, _d, _e))
+ size := uint(Max([]int{len(a), len(b), len(c), len(d), len(e)}))
+
+ result := make([]Out, size)
+
+ for index := uint(0); index < size; index++ {
+ result[index] = iteratee(
+ NthOrEmpty(a, index),
+ NthOrEmpty(b, index),
+ NthOrEmpty(c, index),
+ NthOrEmpty(d, index),
+ NthOrEmpty(e, index),
+ )
}
return result
@@ -415,19 +347,19 @@ func ZipBy5[A, B, C, D, E, Out any](a []A, b []B, c []C, d []D, e []E, iteratee
// When collections are different sizes, the Tuple attributes are filled with zero value.
// Play: https://go.dev/play/p/IK6KVgw9e-S
func ZipBy6[A, B, C, D, E, F, Out any](a []A, b []B, c []C, d []D, e []E, f []F, iteratee func(a A, b B, c C, d D, e E, f F) Out) []Out {
- size := Max([]int{len(a), len(b), len(c), len(d), len(e), len(f)})
-
- result := make([]Out, 0, size)
-
- for index := 0; index < size; index++ {
- _a, _ := Nth(a, index)
- _b, _ := Nth(b, index)
- _c, _ := Nth(c, index)
- _d, _ := Nth(d, index)
- _e, _ := Nth(e, index)
- _f, _ := Nth(f, index)
-
- result = append(result, iteratee(_a, _b, _c, _d, _e, _f))
+ size := uint(Max([]int{len(a), len(b), len(c), len(d), len(e), len(f)}))
+
+ result := make([]Out, size)
+
+ for index := uint(0); index < size; index++ {
+ result[index] = iteratee(
+ NthOrEmpty(a, index),
+ NthOrEmpty(b, index),
+ NthOrEmpty(c, index),
+ NthOrEmpty(d, index),
+ NthOrEmpty(e, index),
+ NthOrEmpty(f, index),
+ )
}
return result
@@ -438,20 +370,20 @@ func ZipBy6[A, B, C, D, E, F, Out any](a []A, b []B, c []C, d []D, e []E, f []F,
// When collections are different sizes, the Tuple attributes are filled with zero value.
// Play: https://go.dev/play/p/4uW6a2vXh8w
func ZipBy7[A, B, C, D, E, F, G, Out any](a []A, b []B, c []C, d []D, e []E, f []F, g []G, iteratee func(a A, b B, c C, d D, e E, f F, g G) Out) []Out {
- size := Max([]int{len(a), len(b), len(c), len(d), len(e), len(f), len(g)})
-
- result := make([]Out, 0, size)
-
- for index := 0; index < size; index++ {
- _a, _ := Nth(a, index)
- _b, _ := Nth(b, index)
- _c, _ := Nth(c, index)
- _d, _ := Nth(d, index)
- _e, _ := Nth(e, index)
- _f, _ := Nth(f, index)
- _g, _ := Nth(g, index)
-
- result = append(result, iteratee(_a, _b, _c, _d, _e, _f, _g))
+ size := uint(Max([]int{len(a), len(b), len(c), len(d), len(e), len(f), len(g)}))
+
+ result := make([]Out, size)
+
+ for index := uint(0); index < size; index++ {
+ result[index] = iteratee(
+ NthOrEmpty(a, index),
+ NthOrEmpty(b, index),
+ NthOrEmpty(c, index),
+ NthOrEmpty(d, index),
+ NthOrEmpty(e, index),
+ NthOrEmpty(f, index),
+ NthOrEmpty(g, index),
+ )
}
return result
@@ -462,21 +394,21 @@ func ZipBy7[A, B, C, D, E, F, G, Out any](a []A, b []B, c []C, d []D, e []E, f [
// When collections are different sizes, the Tuple attributes are filled with zero value.
// Play: https://go.dev/play/p/tk8xW7XzY4v
func ZipBy8[A, B, C, D, E, F, G, H, Out any](a []A, b []B, c []C, d []D, e []E, f []F, g []G, h []H, iteratee func(a A, b B, c C, d D, e E, f F, g G, h H) Out) []Out {
- size := Max([]int{len(a), len(b), len(c), len(d), len(e), len(f), len(g), len(h)})
-
- result := make([]Out, 0, size)
-
- for index := 0; index < size; index++ {
- _a, _ := Nth(a, index)
- _b, _ := Nth(b, index)
- _c, _ := Nth(c, index)
- _d, _ := Nth(d, index)
- _e, _ := Nth(e, index)
- _f, _ := Nth(f, index)
- _g, _ := Nth(g, index)
- _h, _ := Nth(h, index)
-
- result = append(result, iteratee(_a, _b, _c, _d, _e, _f, _g, _h))
+ size := uint(Max([]int{len(a), len(b), len(c), len(d), len(e), len(f), len(g), len(h)}))
+
+ result := make([]Out, size)
+
+ for index := uint(0); index < size; index++ {
+ result[index] = iteratee(
+ NthOrEmpty(a, index),
+ NthOrEmpty(b, index),
+ NthOrEmpty(c, index),
+ NthOrEmpty(d, index),
+ NthOrEmpty(e, index),
+ NthOrEmpty(f, index),
+ NthOrEmpty(g, index),
+ NthOrEmpty(h, index),
+ )
}
return result
@@ -487,25 +419,229 @@ func ZipBy8[A, B, C, D, E, F, G, H, Out any](a []A, b []B, c []C, d []D, e []E,
// When collections are different sizes, the Tuple attributes are filled with zero value.
// Play: https://go.dev/play/p/VGqjDmQ9YqX
func ZipBy9[A, B, C, D, E, F, G, H, I, Out any](a []A, b []B, c []C, d []D, e []E, f []F, g []G, h []H, i []I, iteratee func(a A, b B, c C, d D, e E, f F, g G, h H, i I) Out) []Out {
- size := Max([]int{len(a), len(b), len(c), len(d), len(e), len(f), len(g), len(h), len(i)})
+ size := uint(Max([]int{len(a), len(b), len(c), len(d), len(e), len(f), len(g), len(h), len(i)}))
+
+ result := make([]Out, size)
+
+ for index := uint(0); index < size; index++ {
+ result[index] = iteratee(
+ NthOrEmpty(a, index),
+ NthOrEmpty(b, index),
+ NthOrEmpty(c, index),
+ NthOrEmpty(d, index),
+ NthOrEmpty(e, index),
+ NthOrEmpty(f, index),
+ NthOrEmpty(g, index),
+ NthOrEmpty(h, index),
+ NthOrEmpty(i, index),
+ )
+ }
- result := make([]Out, 0, size)
+ return result
+}
+
+// ZipByErr2 creates a slice of transformed elements, the first of which contains the first elements
+// of the given slices, the second of which contains the second elements of the given slices, and so on.
+// When collections are different sizes, the Tuple attributes are filled with zero value.
+// It returns the first error returned by the iteratee.
+func ZipByErr2[A, B, Out any](a []A, b []B, iteratee func(a A, b B) (Out, error)) ([]Out, error) {
+ size := uint(Max([]int{len(a), len(b)}))
+ result := make([]Out, size)
+
+ for index := uint(0); index < size; index++ {
+ r, err := iteratee(
+ NthOrEmpty(a, index),
+ NthOrEmpty(b, index),
+ )
+ if err != nil {
+ return nil, err
+ }
+ result[index] = r
+ }
- for index := 0; index < size; index++ {
- _a, _ := Nth(a, index)
- _b, _ := Nth(b, index)
- _c, _ := Nth(c, index)
- _d, _ := Nth(d, index)
- _e, _ := Nth(e, index)
- _f, _ := Nth(f, index)
- _g, _ := Nth(g, index)
- _h, _ := Nth(h, index)
- _i, _ := Nth(i, index)
+ return result, nil
+}
- result = append(result, iteratee(_a, _b, _c, _d, _e, _f, _g, _h, _i))
+// ZipByErr3 creates a slice of transformed elements, the first of which contains the first elements
+// of the given slices, the second of which contains the second elements of the given slices, and so on.
+// When collections are different sizes, the Tuple attributes are filled with zero value.
+// It returns the first error returned by the iteratee.
+func ZipByErr3[A, B, C, Out any](a []A, b []B, c []C, iteratee func(a A, b B, c C) (Out, error)) ([]Out, error) {
+ size := uint(Max([]int{len(a), len(b), len(c)}))
+ result := make([]Out, size)
+
+ for index := uint(0); index < size; index++ {
+ r, err := iteratee(
+ NthOrEmpty(a, index),
+ NthOrEmpty(b, index),
+ NthOrEmpty(c, index),
+ )
+ if err != nil {
+ return nil, err
+ }
+ result[index] = r
}
- return result
+ return result, nil
+}
+
+// ZipByErr4 creates a slice of transformed elements, the first of which contains the first elements
+// of the given slices, the second of which contains the second elements of the given slices, and so on.
+// When collections are different sizes, the Tuple attributes are filled with zero value.
+// It returns the first error returned by the iteratee.
+func ZipByErr4[A, B, C, D, Out any](a []A, b []B, c []C, d []D, iteratee func(a A, b B, c C, d D) (Out, error)) ([]Out, error) {
+ size := uint(Max([]int{len(a), len(b), len(c), len(d)}))
+ result := make([]Out, size)
+
+ for index := uint(0); index < size; index++ {
+ r, err := iteratee(
+ NthOrEmpty(a, index),
+ NthOrEmpty(b, index),
+ NthOrEmpty(c, index),
+ NthOrEmpty(d, index),
+ )
+ if err != nil {
+ return nil, err
+ }
+ result[index] = r
+ }
+
+ return result, nil
+}
+
+// ZipByErr5 creates a slice of transformed elements, the first of which contains the first elements
+// of the given slices, the second of which contains the second elements of the given slices, and so on.
+// When collections are different sizes, the Tuple attributes are filled with zero value.
+// It returns the first error returned by the iteratee.
+func ZipByErr5[A, B, C, D, E, Out any](a []A, b []B, c []C, d []D, e []E, iteratee func(a A, b B, c C, d D, e E) (Out, error)) ([]Out, error) {
+ size := uint(Max([]int{len(a), len(b), len(c), len(d), len(e)}))
+ result := make([]Out, size)
+
+ for index := uint(0); index < size; index++ {
+ r, err := iteratee(
+ NthOrEmpty(a, index),
+ NthOrEmpty(b, index),
+ NthOrEmpty(c, index),
+ NthOrEmpty(d, index),
+ NthOrEmpty(e, index),
+ )
+ if err != nil {
+ return nil, err
+ }
+ result[index] = r
+ }
+
+ return result, nil
+}
+
+// ZipByErr6 creates a slice of transformed elements, the first of which contains the first elements
+// of the given slices, the second of which contains the second elements of the given slices, and so on.
+// When collections are different sizes, the Tuple attributes are filled with zero value.
+// It returns the first error returned by the iteratee.
+func ZipByErr6[A, B, C, D, E, F, Out any](a []A, b []B, c []C, d []D, e []E, f []F, iteratee func(a A, b B, c C, d D, e E, f F) (Out, error)) ([]Out, error) {
+ size := uint(Max([]int{len(a), len(b), len(c), len(d), len(e), len(f)}))
+ result := make([]Out, size)
+
+ for index := uint(0); index < size; index++ {
+ r, err := iteratee(
+ NthOrEmpty(a, index),
+ NthOrEmpty(b, index),
+ NthOrEmpty(c, index),
+ NthOrEmpty(d, index),
+ NthOrEmpty(e, index),
+ NthOrEmpty(f, index),
+ )
+ if err != nil {
+ return nil, err
+ }
+ result[index] = r
+ }
+
+ return result, nil
+}
+
+// ZipByErr7 creates a slice of transformed elements, the first of which contains the first elements
+// of the given slices, the second of which contains the second elements of the given slices, and so on.
+// When collections are different sizes, the Tuple attributes are filled with zero value.
+// It returns the first error returned by the iteratee.
+func ZipByErr7[A, B, C, D, E, F, G, Out any](a []A, b []B, c []C, d []D, e []E, f []F, g []G, iteratee func(a A, b B, c C, d D, e E, f F, g G) (Out, error)) ([]Out, error) {
+ size := uint(Max([]int{len(a), len(b), len(c), len(d), len(e), len(f), len(g)}))
+ result := make([]Out, size)
+
+ for index := uint(0); index < size; index++ {
+ r, err := iteratee(
+ NthOrEmpty(a, index),
+ NthOrEmpty(b, index),
+ NthOrEmpty(c, index),
+ NthOrEmpty(d, index),
+ NthOrEmpty(e, index),
+ NthOrEmpty(f, index),
+ NthOrEmpty(g, index),
+ )
+ if err != nil {
+ return nil, err
+ }
+ result[index] = r
+ }
+
+ return result, nil
+}
+
+// ZipByErr8 creates a slice of transformed elements, the first of which contains the first elements
+// of the given slices, the second of which contains the second elements of the given slices, and so on.
+// When collections are different sizes, the Tuple attributes are filled with zero value.
+// It returns the first error returned by the iteratee.
+func ZipByErr8[A, B, C, D, E, F, G, H, Out any](a []A, b []B, c []C, d []D, e []E, f []F, g []G, h []H, iteratee func(a A, b B, c C, d D, e E, f F, g G, h H) (Out, error)) ([]Out, error) {
+ size := uint(Max([]int{len(a), len(b), len(c), len(d), len(e), len(f), len(g), len(h)}))
+ result := make([]Out, size)
+
+ for index := uint(0); index < size; index++ {
+ r, err := iteratee(
+ NthOrEmpty(a, index),
+ NthOrEmpty(b, index),
+ NthOrEmpty(c, index),
+ NthOrEmpty(d, index),
+ NthOrEmpty(e, index),
+ NthOrEmpty(f, index),
+ NthOrEmpty(g, index),
+ NthOrEmpty(h, index),
+ )
+ if err != nil {
+ return nil, err
+ }
+ result[index] = r
+ }
+
+ return result, nil
+}
+
+// ZipByErr9 creates a slice of transformed elements, the first of which contains the first elements
+// of the given slices, the second of which contains the second elements of the given slices, and so on.
+// When collections are different sizes, the Tuple attributes are filled with zero value.
+// It returns the first error returned by the iteratee.
+func ZipByErr9[A, B, C, D, E, F, G, H, I, Out any](a []A, b []B, c []C, d []D, e []E, f []F, g []G, h []H, i []I, iteratee func(a A, b B, c C, d D, e E, f F, g G, h H, i I) (Out, error)) ([]Out, error) {
+ size := uint(Max([]int{len(a), len(b), len(c), len(d), len(e), len(f), len(g), len(h), len(i)}))
+ result := make([]Out, size)
+
+ for index := uint(0); index < size; index++ {
+ r, err := iteratee(
+ NthOrEmpty(a, index),
+ NthOrEmpty(b, index),
+ NthOrEmpty(c, index),
+ NthOrEmpty(d, index),
+ NthOrEmpty(e, index),
+ NthOrEmpty(f, index),
+ NthOrEmpty(g, index),
+ NthOrEmpty(h, index),
+ NthOrEmpty(i, index),
+ )
+ if err != nil {
+ return nil, err
+ }
+ result[index] = r
+ }
+
+ return result, nil
}
// Unzip2 accepts a slice of grouped elements and creates a slice regrouping the elements
@@ -884,6 +1020,222 @@ func UnzipBy9[In, A, B, C, D, E, F, G, H, I any](items []In, iteratee func(In) (
return r1, r2, r3, r4, r5, r6, r7, r8, r9
}
+// UnzipByErr2 iterates over a collection and creates a slice regrouping the elements
+// to their pre-zip configuration.
+// It returns the first error returned by the iteratee.
+func UnzipByErr2[In, A, B any](items []In, iteratee func(In) (a A, b B, err error)) ([]A, []B, error) {
+ size := len(items)
+ r1 := make([]A, 0, size)
+ r2 := make([]B, 0, size)
+
+ for i := range items {
+ a, b, err := iteratee(items[i])
+ if err != nil {
+ return nil, nil, err
+ }
+ r1 = append(r1, a)
+ r2 = append(r2, b)
+ }
+
+ return r1, r2, nil
+}
+
+// UnzipByErr3 iterates over a collection and creates a slice regrouping the elements
+// to their pre-zip configuration.
+// It returns the first error returned by the iteratee.
+func UnzipByErr3[In, A, B, C any](items []In, iteratee func(In) (a A, b B, c C, err error)) ([]A, []B, []C, error) {
+ size := len(items)
+ r1 := make([]A, 0, size)
+ r2 := make([]B, 0, size)
+ r3 := make([]C, 0, size)
+
+ for i := range items {
+ a, b, c, err := iteratee(items[i])
+ if err != nil {
+ return nil, nil, nil, err
+ }
+ r1 = append(r1, a)
+ r2 = append(r2, b)
+ r3 = append(r3, c)
+ }
+
+ return r1, r2, r3, nil
+}
+
+// UnzipByErr4 iterates over a collection and creates a slice regrouping the elements
+// to their pre-zip configuration.
+// It returns the first error returned by the iteratee.
+func UnzipByErr4[In, A, B, C, D any](items []In, iteratee func(In) (a A, b B, c C, d D, err error)) ([]A, []B, []C, []D, error) {
+ size := len(items)
+ r1 := make([]A, 0, size)
+ r2 := make([]B, 0, size)
+ r3 := make([]C, 0, size)
+ r4 := make([]D, 0, size)
+
+ for i := range items {
+ a, b, c, d, err := iteratee(items[i])
+ if err != nil {
+ return nil, nil, nil, nil, err
+ }
+ r1 = append(r1, a)
+ r2 = append(r2, b)
+ r3 = append(r3, c)
+ r4 = append(r4, d)
+ }
+
+ return r1, r2, r3, r4, nil
+}
+
+// UnzipByErr5 iterates over a collection and creates a slice regrouping the elements
+// to their pre-zip configuration.
+// It returns the first error returned by the iteratee.
+func UnzipByErr5[In, A, B, C, D, E any](items []In, iteratee func(In) (a A, b B, c C, d D, e E, err error)) ([]A, []B, []C, []D, []E, error) {
+ size := len(items)
+ r1 := make([]A, 0, size)
+ r2 := make([]B, 0, size)
+ r3 := make([]C, 0, size)
+ r4 := make([]D, 0, size)
+ r5 := make([]E, 0, size)
+
+ for i := range items {
+ a, b, c, d, e, err := iteratee(items[i])
+ if err != nil {
+ return nil, nil, nil, nil, nil, err
+ }
+ r1 = append(r1, a)
+ r2 = append(r2, b)
+ r3 = append(r3, c)
+ r4 = append(r4, d)
+ r5 = append(r5, e)
+ }
+
+ return r1, r2, r3, r4, r5, nil
+}
+
+// UnzipByErr6 iterates over a collection and creates a slice regrouping the elements
+// to their pre-zip configuration.
+// It returns the first error returned by the iteratee.
+func UnzipByErr6[In, A, B, C, D, E, F any](items []In, iteratee func(In) (a A, b B, c C, d D, e E, f F, err error)) ([]A, []B, []C, []D, []E, []F, error) {
+ size := len(items)
+ r1 := make([]A, 0, size)
+ r2 := make([]B, 0, size)
+ r3 := make([]C, 0, size)
+ r4 := make([]D, 0, size)
+ r5 := make([]E, 0, size)
+ r6 := make([]F, 0, size)
+
+ for i := range items {
+ a, b, c, d, e, f, err := iteratee(items[i])
+ if err != nil {
+ return nil, nil, nil, nil, nil, nil, err
+ }
+ r1 = append(r1, a)
+ r2 = append(r2, b)
+ r3 = append(r3, c)
+ r4 = append(r4, d)
+ r5 = append(r5, e)
+ r6 = append(r6, f)
+ }
+
+ return r1, r2, r3, r4, r5, r6, nil
+}
+
+// UnzipByErr7 iterates over a collection and creates a slice regrouping the elements
+// to their pre-zip configuration.
+// It returns the first error returned by the iteratee.
+func UnzipByErr7[In, A, B, C, D, E, F, G any](items []In, iteratee func(In) (a A, b B, c C, d D, e E, f F, g G, err error)) ([]A, []B, []C, []D, []E, []F, []G, error) {
+ size := len(items)
+ r1 := make([]A, 0, size)
+ r2 := make([]B, 0, size)
+ r3 := make([]C, 0, size)
+ r4 := make([]D, 0, size)
+ r5 := make([]E, 0, size)
+ r6 := make([]F, 0, size)
+ r7 := make([]G, 0, size)
+
+ for i := range items {
+ a, b, c, d, e, f, g, err := iteratee(items[i])
+ if err != nil {
+ return nil, nil, nil, nil, nil, nil, nil, err
+ }
+ r1 = append(r1, a)
+ r2 = append(r2, b)
+ r3 = append(r3, c)
+ r4 = append(r4, d)
+ r5 = append(r5, e)
+ r6 = append(r6, f)
+ r7 = append(r7, g)
+ }
+
+ return r1, r2, r3, r4, r5, r6, r7, nil
+}
+
+// UnzipByErr8 iterates over a collection and creates a slice regrouping the elements
+// to their pre-zip configuration.
+// It returns the first error returned by the iteratee.
+func UnzipByErr8[In, A, B, C, D, E, F, G, H any](items []In, iteratee func(In) (a A, b B, c C, d D, e E, f F, g G, h H, err error)) ([]A, []B, []C, []D, []E, []F, []G, []H, error) {
+ size := len(items)
+ r1 := make([]A, 0, size)
+ r2 := make([]B, 0, size)
+ r3 := make([]C, 0, size)
+ r4 := make([]D, 0, size)
+ r5 := make([]E, 0, size)
+ r6 := make([]F, 0, size)
+ r7 := make([]G, 0, size)
+ r8 := make([]H, 0, size)
+
+ for i := range items {
+ a, b, c, d, e, f, g, h, err := iteratee(items[i])
+ if err != nil {
+ return nil, nil, nil, nil, nil, nil, nil, nil, err
+ }
+ r1 = append(r1, a)
+ r2 = append(r2, b)
+ r3 = append(r3, c)
+ r4 = append(r4, d)
+ r5 = append(r5, e)
+ r6 = append(r6, f)
+ r7 = append(r7, g)
+ r8 = append(r8, h)
+ }
+
+ return r1, r2, r3, r4, r5, r6, r7, r8, nil
+}
+
+// UnzipByErr9 iterates over a collection and creates a slice regrouping the elements
+// to their pre-zip configuration.
+// It returns the first error returned by the iteratee.
+func UnzipByErr9[In, A, B, C, D, E, F, G, H, I any](items []In, iteratee func(In) (a A, b B, c C, d D, e E, f F, g G, h H, i I, err error)) ([]A, []B, []C, []D, []E, []F, []G, []H, []I, error) {
+ size := len(items)
+ r1 := make([]A, 0, size)
+ r2 := make([]B, 0, size)
+ r3 := make([]C, 0, size)
+ r4 := make([]D, 0, size)
+ r5 := make([]E, 0, size)
+ r6 := make([]F, 0, size)
+ r7 := make([]G, 0, size)
+ r8 := make([]H, 0, size)
+ r9 := make([]I, 0, size)
+
+ for i := range items {
+ a, b, c, d, e, f, g, h, i, err := iteratee(items[i])
+ if err != nil {
+ return nil, nil, nil, nil, nil, nil, nil, nil, nil, err
+ }
+ r1 = append(r1, a)
+ r2 = append(r2, b)
+ r3 = append(r3, c)
+ r4 = append(r4, d)
+ r5 = append(r5, e)
+ r6 = append(r6, f)
+ r7 = append(r7, g)
+ r8 = append(r8, h)
+ r9 = append(r9, i)
+ }
+
+ return r1, r2, r3, r4, r5, r6, r7, r8, r9, nil
+}
+
// CrossJoin2 combines every item from one list with every item from others.
// It is the cartesian product of lists received as arguments.
// Returns an empty list if a list is empty.
@@ -949,11 +1301,11 @@ func CrossJoin9[A, B, C, D, E, F, G, H, I any](listA []A, listB []B, listC []C,
}
// CrossJoinBy2 combines every item from one list with every item from others.
-// It is the cartesian product of lists received as arguments. The project function
+// It is the cartesian product of lists received as arguments. The transform function
// is used to create the output values.
// Returns an empty list if a list is empty.
// Play: https://go.dev/play/p/8Y7btpvuA-C
-func CrossJoinBy2[A, B, Out any](listA []A, listB []B, project func(a A, b B) Out) []Out {
+func CrossJoinBy2[A, B, Out any](listA []A, listB []B, transform func(a A, b B) Out) []Out {
size := len(listA) * len(listB)
if size == 0 {
return []Out{}
@@ -963,7 +1315,7 @@ func CrossJoinBy2[A, B, Out any](listA []A, listB []B, project func(a A, b B) Ou
for _, a := range listA {
for _, b := range listB {
- result = append(result, project(a, b))
+ result = append(result, transform(a, b))
}
}
@@ -971,11 +1323,11 @@ func CrossJoinBy2[A, B, Out any](listA []A, listB []B, project func(a A, b B) Ou
}
// CrossJoinBy3 combines every item from one list with every item from others.
-// It is the cartesian product of lists received as arguments. The project function
+// It is the cartesian product of lists received as arguments. The transform function
// is used to create the output values.
// Returns an empty list if a list is empty.
// Play: https://go.dev/play/p/3z4y5x6w7v8
-func CrossJoinBy3[A, B, C, Out any](listA []A, listB []B, listC []C, project func(a A, b B, c C) Out) []Out {
+func CrossJoinBy3[A, B, C, Out any](listA []A, listB []B, listC []C, transform func(a A, b B, c C) Out) []Out {
size := len(listA) * len(listB) * len(listC)
if size == 0 {
return []Out{}
@@ -986,7 +1338,7 @@ func CrossJoinBy3[A, B, C, Out any](listA []A, listB []B, listC []C, project fun
for _, a := range listA {
for _, b := range listB {
for _, c := range listC {
- result = append(result, project(a, b, c))
+ result = append(result, transform(a, b, c))
}
}
}
@@ -995,11 +1347,11 @@ func CrossJoinBy3[A, B, C, Out any](listA []A, listB []B, listC []C, project fun
}
// CrossJoinBy4 combines every item from one list with every item from others.
-// It is the cartesian product of lists received as arguments. The project function
+// It is the cartesian product of lists received as arguments. The transform function
// is used to create the output values.
// Returns an empty list if a list is empty.
// Play: https://go.dev/play/p/8b9c0d1e2f3
-func CrossJoinBy4[A, B, C, D, Out any](listA []A, listB []B, listC []C, listD []D, project func(a A, b B, c C, d D) Out) []Out {
+func CrossJoinBy4[A, B, C, D, Out any](listA []A, listB []B, listC []C, listD []D, transform func(a A, b B, c C, d D) Out) []Out {
size := len(listA) * len(listB) * len(listC) * len(listD)
if size == 0 {
return []Out{}
@@ -1011,7 +1363,7 @@ func CrossJoinBy4[A, B, C, D, Out any](listA []A, listB []B, listC []C, listD []
for _, b := range listB {
for _, c := range listC {
for _, d := range listD {
- result = append(result, project(a, b, c, d))
+ result = append(result, transform(a, b, c, d))
}
}
}
@@ -1021,11 +1373,11 @@ func CrossJoinBy4[A, B, C, D, Out any](listA []A, listB []B, listC []C, listD []
}
// CrossJoinBy5 combines every item from one list with every item from others.
-// It is the cartesian product of lists received as arguments. The project function
+// It is the cartesian product of lists received as arguments. The transform function
// is used to create the output values.
// Returns an empty list if a list is empty.
// Play: https://go.dev/play/p/4g5h6i7j8k9
-func CrossJoinBy5[A, B, C, D, E, Out any](listA []A, listB []B, listC []C, listD []D, listE []E, project func(a A, b B, c C, d D, e E) Out) []Out {
+func CrossJoinBy5[A, B, C, D, E, Out any](listA []A, listB []B, listC []C, listD []D, listE []E, transform func(a A, b B, c C, d D, e E) Out) []Out {
size := len(listA) * len(listB) * len(listC) * len(listD) * len(listE)
if size == 0 {
return []Out{}
@@ -1038,7 +1390,7 @@ func CrossJoinBy5[A, B, C, D, E, Out any](listA []A, listB []B, listC []C, listD
for _, c := range listC {
for _, d := range listD {
for _, e := range listE {
- result = append(result, project(a, b, c, d, e))
+ result = append(result, transform(a, b, c, d, e))
}
}
}
@@ -1049,11 +1401,11 @@ func CrossJoinBy5[A, B, C, D, E, Out any](listA []A, listB []B, listC []C, listD
}
// CrossJoinBy6 combines every item from one list with every item from others.
-// It is the cartesian product of lists received as arguments. The project function
+// It is the cartesian product of lists received as arguments. The transform function
// is used to create the output values.
// Returns an empty list if a list is empty.
// Play: https://go.dev/play/p/1l2m3n4o5p6
-func CrossJoinBy6[A, B, C, D, E, F, Out any](listA []A, listB []B, listC []C, listD []D, listE []E, listF []F, project func(a A, b B, c C, d D, e E, f F) Out) []Out {
+func CrossJoinBy6[A, B, C, D, E, F, Out any](listA []A, listB []B, listC []C, listD []D, listE []E, listF []F, transform func(a A, b B, c C, d D, e E, f F) Out) []Out {
size := len(listA) * len(listB) * len(listC) * len(listD) * len(listE) * len(listF)
if size == 0 {
return []Out{}
@@ -1067,7 +1419,7 @@ func CrossJoinBy6[A, B, C, D, E, F, Out any](listA []A, listB []B, listC []C, li
for _, d := range listD {
for _, e := range listE {
for _, f := range listF {
- result = append(result, project(a, b, c, d, e, f))
+ result = append(result, transform(a, b, c, d, e, f))
}
}
}
@@ -1079,11 +1431,11 @@ func CrossJoinBy6[A, B, C, D, E, F, Out any](listA []A, listB []B, listC []C, li
}
// CrossJoinBy7 combines every item from one list with every item from others.
-// It is the cartesian product of lists received as arguments. The project function
+// It is the cartesian product of lists received as arguments. The transform function
// is used to create the output values.
// Returns an empty list if a list is empty.
// Play: https://go.dev/play/p/7q8r9s0t1u2
-func CrossJoinBy7[A, B, C, D, E, F, G, Out any](listA []A, listB []B, listC []C, listD []D, listE []E, listF []F, listG []G, project func(a A, b B, c C, d D, e E, f F, g G) Out) []Out {
+func CrossJoinBy7[A, B, C, D, E, F, G, Out any](listA []A, listB []B, listC []C, listD []D, listE []E, listF []F, listG []G, transform func(a A, b B, c C, d D, e E, f F, g G) Out) []Out {
size := len(listA) * len(listB) * len(listC) * len(listD) * len(listE) * len(listF) * len(listG)
if size == 0 {
return []Out{}
@@ -1098,7 +1450,7 @@ func CrossJoinBy7[A, B, C, D, E, F, G, Out any](listA []A, listB []B, listC []C,
for _, e := range listE {
for _, f := range listF {
for _, g := range listG {
- result = append(result, project(a, b, c, d, e, f, g))
+ result = append(result, transform(a, b, c, d, e, f, g))
}
}
}
@@ -1111,11 +1463,11 @@ func CrossJoinBy7[A, B, C, D, E, F, G, Out any](listA []A, listB []B, listC []C,
}
// CrossJoinBy8 combines every item from one list with every item from others.
-// It is the cartesian product of lists received as arguments. The project function
+// It is the cartesian product of lists received as arguments. The transform function
// is used to create the output values.
// Returns an empty list if a list is empty.
// Play: https://go.dev/play/p/3v4w5x6y7z8
-func CrossJoinBy8[A, B, C, D, E, F, G, H, Out any](listA []A, listB []B, listC []C, listD []D, listE []E, listF []F, listG []G, listH []H, project func(a A, b B, c C, d D, e E, f F, g G, h H) Out) []Out {
+func CrossJoinBy8[A, B, C, D, E, F, G, H, Out any](listA []A, listB []B, listC []C, listD []D, listE []E, listF []F, listG []G, listH []H, transform func(a A, b B, c C, d D, e E, f F, g G, h H) Out) []Out {
size := len(listA) * len(listB) * len(listC) * len(listD) * len(listE) * len(listF) * len(listG) * len(listH)
if size == 0 {
return []Out{}
@@ -1131,7 +1483,7 @@ func CrossJoinBy8[A, B, C, D, E, F, G, H, Out any](listA []A, listB []B, listC [
for _, f := range listF {
for _, g := range listG {
for _, h := range listH {
- result = append(result, project(a, b, c, d, e, f, g, h))
+ result = append(result, transform(a, b, c, d, e, f, g, h))
}
}
}
@@ -1145,11 +1497,11 @@ func CrossJoinBy8[A, B, C, D, E, F, G, H, Out any](listA []A, listB []B, listC [
}
// CrossJoinBy9 combines every item from one list with every item from others.
-// It is the cartesian product of lists received as arguments. The project function
+// It is the cartesian product of lists received as arguments. The transform function
// is used to create the output values.
// Returns an empty list if a list is empty.
// Play: https://go.dev/play/p/9a0b1c2d3e4
-func CrossJoinBy9[A, B, C, D, E, F, G, H, I, Out any](listA []A, listB []B, listC []C, listD []D, listE []E, listF []F, listG []G, listH []H, listI []I, project func(a A, b B, c C, d D, e E, f F, g G, h H, i I) Out) []Out {
+func CrossJoinBy9[A, B, C, D, E, F, G, H, I, Out any](listA []A, listB []B, listC []C, listD []D, listE []E, listF []F, listG []G, listH []H, listI []I, transform func(a A, b B, c C, d D, e E, f F, g G, h H, i I) Out) []Out {
size := len(listA) * len(listB) * len(listC) * len(listD) * len(listE) * len(listF) * len(listG) * len(listH) * len(listI)
if size == 0 {
return []Out{}
@@ -1166,7 +1518,7 @@ func CrossJoinBy9[A, B, C, D, E, F, G, H, I, Out any](listA []A, listB []B, list
for _, g := range listG {
for _, h := range listH {
for _, i := range listI {
- result = append(result, project(a, b, c, d, e, f, g, h, i))
+ result = append(result, transform(a, b, c, d, e, f, g, h, i))
}
}
}
@@ -1179,3 +1531,267 @@ func CrossJoinBy9[A, B, C, D, E, F, G, H, I, Out any](listA []A, listB []B, list
return result
}
+
+// CrossJoinByErr2 combines every item from one list with every item from others.
+// It is the cartesian product of lists received as arguments. The transform function
+// is used to create the output values.
+// Returns an empty list if a list is empty.
+// It returns the first error returned by the transform function.
+func CrossJoinByErr2[A, B, Out any](listA []A, listB []B, transform func(a A, b B) (Out, error)) ([]Out, error) {
+ size := len(listA) * len(listB)
+ if size == 0 {
+ return []Out{}, nil
+ }
+
+ result := make([]Out, 0, size)
+
+ for _, a := range listA {
+ for _, b := range listB {
+ r, err := transform(a, b)
+ if err != nil {
+ return nil, err
+ }
+ result = append(result, r)
+ }
+ }
+
+ return result, nil
+}
+
+// CrossJoinByErr3 combines every item from one list with every item from others.
+// It is the cartesian product of lists received as arguments. The transform function
+// is used to create the output values.
+// Returns an empty list if a list is empty.
+// It returns the first error returned by the transform function.
+func CrossJoinByErr3[A, B, C, Out any](listA []A, listB []B, listC []C, transform func(a A, b B, c C) (Out, error)) ([]Out, error) {
+ size := len(listA) * len(listB) * len(listC)
+ if size == 0 {
+ return []Out{}, nil
+ }
+
+ result := make([]Out, 0, size)
+
+ for _, a := range listA {
+ for _, b := range listB {
+ for _, c := range listC {
+ r, err := transform(a, b, c)
+ if err != nil {
+ return nil, err
+ }
+ result = append(result, r)
+ }
+ }
+ }
+
+ return result, nil
+}
+
+// CrossJoinByErr4 combines every item from one list with every item from others.
+// It is the cartesian product of lists received as arguments. The transform function
+// is used to create the output values.
+// Returns an empty list if a list is empty.
+// It returns the first error returned by the transform function.
+func CrossJoinByErr4[A, B, C, D, Out any](listA []A, listB []B, listC []C, listD []D, transform func(a A, b B, c C, d D) (Out, error)) ([]Out, error) {
+ size := len(listA) * len(listB) * len(listC) * len(listD)
+ if size == 0 {
+ return []Out{}, nil
+ }
+
+ result := make([]Out, 0, size)
+
+ for _, a := range listA {
+ for _, b := range listB {
+ for _, c := range listC {
+ for _, d := range listD {
+ r, err := transform(a, b, c, d)
+ if err != nil {
+ return nil, err
+ }
+ result = append(result, r)
+ }
+ }
+ }
+ }
+
+ return result, nil
+}
+
+// CrossJoinByErr5 combines every item from one list with every item from others.
+// It is the cartesian product of lists received as arguments. The transform function
+// is used to create the output values.
+// Returns an empty list if a list is empty.
+// It returns the first error returned by the transform function.
+func CrossJoinByErr5[A, B, C, D, E, Out any](listA []A, listB []B, listC []C, listD []D, listE []E, transform func(a A, b B, c C, d D, e E) (Out, error)) ([]Out, error) {
+ size := len(listA) * len(listB) * len(listC) * len(listD) * len(listE)
+ if size == 0 {
+ return []Out{}, nil
+ }
+
+ result := make([]Out, 0, size)
+
+ for _, a := range listA {
+ for _, b := range listB {
+ for _, c := range listC {
+ for _, d := range listD {
+ for _, e := range listE {
+ r, err := transform(a, b, c, d, e)
+ if err != nil {
+ return nil, err
+ }
+ result = append(result, r)
+ }
+ }
+ }
+ }
+ }
+
+ return result, nil
+}
+
+// CrossJoinByErr6 combines every item from one list with every item from others.
+// It is the cartesian product of lists received as arguments. The transform function
+// is used to create the output values.
+// Returns an empty list if a list is empty.
+// It returns the first error returned by the transform function.
+func CrossJoinByErr6[A, B, C, D, E, F, Out any](listA []A, listB []B, listC []C, listD []D, listE []E, listF []F, transform func(a A, b B, c C, d D, e E, f F) (Out, error)) ([]Out, error) {
+ size := len(listA) * len(listB) * len(listC) * len(listD) * len(listE) * len(listF)
+ if size == 0 {
+ return []Out{}, nil
+ }
+
+ result := make([]Out, 0, size)
+
+ for _, a := range listA {
+ for _, b := range listB {
+ for _, c := range listC {
+ for _, d := range listD {
+ for _, e := range listE {
+ for _, f := range listF {
+ r, err := transform(a, b, c, d, e, f)
+ if err != nil {
+ return nil, err
+ }
+ result = append(result, r)
+ }
+ }
+ }
+ }
+ }
+ }
+
+ return result, nil
+}
+
+// CrossJoinByErr7 combines every item from one list with every item from others.
+// It is the cartesian product of lists received as arguments. The transform function
+// is used to create the output values.
+// Returns an empty list if a list is empty.
+// It returns the first error returned by the transform function.
+func CrossJoinByErr7[A, B, C, D, E, F, G, Out any](listA []A, listB []B, listC []C, listD []D, listE []E, listF []F, listG []G, transform func(a A, b B, c C, d D, e E, f F, g G) (Out, error)) ([]Out, error) {
+ size := len(listA) * len(listB) * len(listC) * len(listD) * len(listE) * len(listF) * len(listG)
+ if size == 0 {
+ return []Out{}, nil
+ }
+
+ result := make([]Out, 0, size)
+
+ for _, a := range listA {
+ for _, b := range listB {
+ for _, c := range listC {
+ for _, d := range listD {
+ for _, e := range listE {
+ for _, f := range listF {
+ for _, g := range listG {
+ r, err := transform(a, b, c, d, e, f, g)
+ if err != nil {
+ return nil, err
+ }
+ result = append(result, r)
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+
+ return result, nil
+}
+
+// CrossJoinByErr8 combines every item from one list with every item from others.
+// It is the cartesian product of lists received as arguments. The transform function
+// is used to create the output values.
+// Returns an empty list if a list is empty.
+// It returns the first error returned by the transform function.
+func CrossJoinByErr8[A, B, C, D, E, F, G, H, Out any](listA []A, listB []B, listC []C, listD []D, listE []E, listF []F, listG []G, listH []H, transform func(a A, b B, c C, d D, e E, f F, g G, h H) (Out, error)) ([]Out, error) {
+ size := len(listA) * len(listB) * len(listC) * len(listD) * len(listE) * len(listF) * len(listG) * len(listH)
+ if size == 0 {
+ return []Out{}, nil
+ }
+
+ result := make([]Out, 0, size)
+
+ for _, a := range listA {
+ for _, b := range listB {
+ for _, c := range listC {
+ for _, d := range listD {
+ for _, e := range listE {
+ for _, f := range listF {
+ for _, g := range listG {
+ for _, h := range listH {
+ r, err := transform(a, b, c, d, e, f, g, h)
+ if err != nil {
+ return nil, err
+ }
+ result = append(result, r)
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+
+ return result, nil
+}
+
+// CrossJoinByErr9 combines every item from one list with every item from others.
+// It is the cartesian product of lists received as arguments. The transform function
+// is used to create the output values.
+// Returns an empty list if a list is empty.
+// It returns the first error returned by the transform function.
+func CrossJoinByErr9[A, B, C, D, E, F, G, H, I, Out any](listA []A, listB []B, listC []C, listD []D, listE []E, listF []F, listG []G, listH []H, listI []I, transform func(a A, b B, c C, d D, e E, f F, g G, h H, i I) (Out, error)) ([]Out, error) {
+ size := len(listA) * len(listB) * len(listC) * len(listD) * len(listE) * len(listF) * len(listG) * len(listH) * len(listI)
+ if size == 0 {
+ return []Out{}, nil
+ }
+
+ result := make([]Out, 0, size)
+
+ for _, a := range listA {
+ for _, b := range listB {
+ for _, c := range listC {
+ for _, d := range listD {
+ for _, e := range listE {
+ for _, f := range listF {
+ for _, g := range listG {
+ for _, h := range listH {
+ for _, i := range listI {
+ r, err := transform(a, b, c, d, e, f, g, h, i)
+ if err != nil {
+ return nil, err
+ }
+ result = append(result, r)
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+
+ return result, nil
+}
diff --git a/vendor/github.com/samber/lo/type_manipulation.go b/vendor/github.com/samber/lo/type_manipulation.go
index 281f316..c2f93d7 100644
--- a/vendor/github.com/samber/lo/type_manipulation.go
+++ b/vendor/github.com/samber/lo/type_manipulation.go
@@ -173,7 +173,7 @@ func CoalesceOrEmpty[T comparable](v ...T) T {
// Play: https://go.dev/play/p/Gyo9otyvFHH
func CoalesceSlice[T any](v ...[]T) ([]T, bool) {
for i := range v {
- if v[i] != nil && len(v[i]) > 0 {
+ if len(v[i]) > 0 {
return v[i], true
}
}
@@ -184,7 +184,7 @@ func CoalesceSlice[T any](v ...[]T) ([]T, bool) {
// Play: https://go.dev/play/p/Gyo9otyvFHH
func CoalesceSliceOrEmpty[T any](v ...[]T) []T {
for i := range v {
- if v[i] != nil && len(v[i]) > 0 {
+ if len(v[i]) > 0 {
return v[i]
}
}
@@ -195,7 +195,7 @@ func CoalesceSliceOrEmpty[T any](v ...[]T) []T {
// Play: https://go.dev/play/p/Gyo9otyvFHH
func CoalesceMap[K comparable, V any](v ...map[K]V) (map[K]V, bool) {
for i := range v {
- if v[i] != nil && len(v[i]) > 0 {
+ if len(v[i]) > 0 {
return v[i], true
}
}
@@ -206,7 +206,7 @@ func CoalesceMap[K comparable, V any](v ...map[K]V) (map[K]V, bool) {
// Play: https://go.dev/play/p/Gyo9otyvFHH
func CoalesceMapOrEmpty[K comparable, V any](v ...map[K]V) map[K]V {
for i := range v {
- if v[i] != nil && len(v[i]) > 0 {
+ if len(v[i]) > 0 {
return v[i]
}
}
diff --git a/vendor/github.com/youmark/pkcs8/.gitignore b/vendor/github.com/youmark/pkcs8/.gitignore
new file mode 100644
index 0000000..8365624
--- /dev/null
+++ b/vendor/github.com/youmark/pkcs8/.gitignore
@@ -0,0 +1,23 @@
+# Compiled Object files, Static and Dynamic libs (Shared Objects)
+*.o
+*.a
+*.so
+
+# Folders
+_obj
+_test
+
+# Architecture specific extensions/prefixes
+*.[568vq]
+[568vq].out
+
+*.cgo1.go
+*.cgo2.c
+_cgo_defun.c
+_cgo_gotypes.go
+_cgo_export.*
+
+_testmain.go
+
+*.exe
+*.test
diff --git a/vendor/github.com/youmark/pkcs8/LICENSE b/vendor/github.com/youmark/pkcs8/LICENSE
new file mode 100644
index 0000000..c939f44
--- /dev/null
+++ b/vendor/github.com/youmark/pkcs8/LICENSE
@@ -0,0 +1,21 @@
+The MIT License (MIT)
+
+Copyright (c) 2014 youmark
+
+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.
\ No newline at end of file
diff --git a/vendor/github.com/youmark/pkcs8/README b/vendor/github.com/youmark/pkcs8/README
new file mode 100644
index 0000000..376fcaf
--- /dev/null
+++ b/vendor/github.com/youmark/pkcs8/README
@@ -0,0 +1 @@
+pkcs8 package: implement PKCS#8 private key parsing and conversion as defined in RFC5208 and RFC5958
diff --git a/vendor/github.com/youmark/pkcs8/README.md b/vendor/github.com/youmark/pkcs8/README.md
new file mode 100644
index 0000000..ef6c762
--- /dev/null
+++ b/vendor/github.com/youmark/pkcs8/README.md
@@ -0,0 +1,22 @@
+pkcs8
+===
+OpenSSL can generate private keys in both "traditional format" and PKCS#8 format. Newer applications are advised to use more secure PKCS#8 format. Go standard crypto package provides a [function](http://golang.org/pkg/crypto/x509/#ParsePKCS8PrivateKey) to parse private key in PKCS#8 format. There is a limitation to this function. It can only handle unencrypted PKCS#8 private keys. To use this function, the user has to save the private key in file without encryption, which is a bad practice to leave private keys unprotected on file systems. In addition, Go standard package lacks the functions to convert RSA/ECDSA private keys into PKCS#8 format.
+
+pkcs8 package fills the gap here. It implements functions to process private keys in PKCS#8 format, as defined in [RFC5208](https://tools.ietf.org/html/rfc5208) and [RFC5958](https://tools.ietf.org/html/rfc5958). It can handle both unencrypted PKCS#8 PrivateKeyInfo format and EncryptedPrivateKeyInfo format with PKCS#5 (v2.0) algorithms.
+
+
+[**Godoc**](http://godoc.org/github.com/youmark/pkcs8)
+
+## Installation
+Supports Go 1.10+. Release v1.1 is the last release supporting Go 1.9
+
+```text
+go get github.com/youmark/pkcs8
+```
+## dependency
+This package depends on golang.org/x/crypto/pbkdf2 and golang.org/x/crypto/scrypt packages. Use the following command to retrieve them
+```text
+go get golang.org/x/crypto/pbkdf2
+go get golang.org/x/crypto/scrypt
+```
+
diff --git a/vendor/github.com/youmark/pkcs8/cipher.go b/vendor/github.com/youmark/pkcs8/cipher.go
new file mode 100644
index 0000000..2946c93
--- /dev/null
+++ b/vendor/github.com/youmark/pkcs8/cipher.go
@@ -0,0 +1,60 @@
+package pkcs8
+
+import (
+ "bytes"
+ "crypto/cipher"
+ "encoding/asn1"
+)
+
+type cipherWithBlock struct {
+ oid asn1.ObjectIdentifier
+ ivSize int
+ keySize int
+ newBlock func(key []byte) (cipher.Block, error)
+}
+
+func (c cipherWithBlock) IVSize() int {
+ return c.ivSize
+}
+
+func (c cipherWithBlock) KeySize() int {
+ return c.keySize
+}
+
+func (c cipherWithBlock) OID() asn1.ObjectIdentifier {
+ return c.oid
+}
+
+func (c cipherWithBlock) Encrypt(key, iv, plaintext []byte) ([]byte, error) {
+ block, err := c.newBlock(key)
+ if err != nil {
+ return nil, err
+ }
+ return cbcEncrypt(block, key, iv, plaintext)
+}
+
+func (c cipherWithBlock) Decrypt(key, iv, ciphertext []byte) ([]byte, error) {
+ block, err := c.newBlock(key)
+ if err != nil {
+ return nil, err
+ }
+ return cbcDecrypt(block, key, iv, ciphertext)
+}
+
+func cbcEncrypt(block cipher.Block, key, iv, plaintext []byte) ([]byte, error) {
+ mode := cipher.NewCBCEncrypter(block, iv)
+ paddingLen := block.BlockSize() - (len(plaintext) % block.BlockSize())
+ ciphertext := make([]byte, len(plaintext)+paddingLen)
+ copy(ciphertext, plaintext)
+ copy(ciphertext[len(plaintext):], bytes.Repeat([]byte{byte(paddingLen)}, paddingLen))
+ mode.CryptBlocks(ciphertext, ciphertext)
+ return ciphertext, nil
+}
+
+func cbcDecrypt(block cipher.Block, key, iv, ciphertext []byte) ([]byte, error) {
+ mode := cipher.NewCBCDecrypter(block, iv)
+ plaintext := make([]byte, len(ciphertext))
+ mode.CryptBlocks(plaintext, ciphertext)
+ // TODO: remove padding
+ return plaintext, nil
+}
diff --git a/vendor/github.com/youmark/pkcs8/cipher_3des.go b/vendor/github.com/youmark/pkcs8/cipher_3des.go
new file mode 100644
index 0000000..5629664
--- /dev/null
+++ b/vendor/github.com/youmark/pkcs8/cipher_3des.go
@@ -0,0 +1,24 @@
+package pkcs8
+
+import (
+ "crypto/des"
+ "encoding/asn1"
+)
+
+var (
+ oidDESEDE3CBC = asn1.ObjectIdentifier{1, 2, 840, 113549, 3, 7}
+)
+
+func init() {
+ RegisterCipher(oidDESEDE3CBC, func() Cipher {
+ return TripleDESCBC
+ })
+}
+
+// TripleDESCBC is the 168-bit key 3DES cipher in CBC mode.
+var TripleDESCBC = cipherWithBlock{
+ ivSize: des.BlockSize,
+ keySize: 24,
+ newBlock: des.NewTripleDESCipher,
+ oid: oidDESEDE3CBC,
+}
diff --git a/vendor/github.com/youmark/pkcs8/cipher_aes.go b/vendor/github.com/youmark/pkcs8/cipher_aes.go
new file mode 100644
index 0000000..c0372d1
--- /dev/null
+++ b/vendor/github.com/youmark/pkcs8/cipher_aes.go
@@ -0,0 +1,84 @@
+package pkcs8
+
+import (
+ "crypto/aes"
+ "encoding/asn1"
+)
+
+var (
+ oidAES128CBC = asn1.ObjectIdentifier{2, 16, 840, 1, 101, 3, 4, 1, 2}
+ oidAES128GCM = asn1.ObjectIdentifier{2, 16, 840, 1, 101, 3, 4, 1, 6}
+ oidAES192CBC = asn1.ObjectIdentifier{2, 16, 840, 1, 101, 3, 4, 1, 22}
+ oidAES192GCM = asn1.ObjectIdentifier{2, 16, 840, 1, 101, 3, 4, 1, 26}
+ oidAES256CBC = asn1.ObjectIdentifier{2, 16, 840, 1, 101, 3, 4, 1, 42}
+ oidAES256GCM = asn1.ObjectIdentifier{2, 16, 840, 1, 101, 3, 4, 1, 46}
+)
+
+func init() {
+ RegisterCipher(oidAES128CBC, func() Cipher {
+ return AES128CBC
+ })
+ RegisterCipher(oidAES128GCM, func() Cipher {
+ return AES128GCM
+ })
+ RegisterCipher(oidAES192CBC, func() Cipher {
+ return AES192CBC
+ })
+ RegisterCipher(oidAES192GCM, func() Cipher {
+ return AES192GCM
+ })
+ RegisterCipher(oidAES256CBC, func() Cipher {
+ return AES256CBC
+ })
+ RegisterCipher(oidAES256GCM, func() Cipher {
+ return AES256GCM
+ })
+}
+
+// AES128CBC is the 128-bit key AES cipher in CBC mode.
+var AES128CBC = cipherWithBlock{
+ ivSize: aes.BlockSize,
+ keySize: 16,
+ newBlock: aes.NewCipher,
+ oid: oidAES128CBC,
+}
+
+// AES128GCM is the 128-bit key AES cipher in GCM mode.
+var AES128GCM = cipherWithBlock{
+ ivSize: aes.BlockSize,
+ keySize: 16,
+ newBlock: aes.NewCipher,
+ oid: oidAES128GCM,
+}
+
+// AES192CBC is the 192-bit key AES cipher in CBC mode.
+var AES192CBC = cipherWithBlock{
+ ivSize: aes.BlockSize,
+ keySize: 24,
+ newBlock: aes.NewCipher,
+ oid: oidAES192CBC,
+}
+
+// AES192GCM is the 912-bit key AES cipher in GCM mode.
+var AES192GCM = cipherWithBlock{
+ ivSize: aes.BlockSize,
+ keySize: 24,
+ newBlock: aes.NewCipher,
+ oid: oidAES192GCM,
+}
+
+// AES256CBC is the 256-bit key AES cipher in CBC mode.
+var AES256CBC = cipherWithBlock{
+ ivSize: aes.BlockSize,
+ keySize: 32,
+ newBlock: aes.NewCipher,
+ oid: oidAES256CBC,
+}
+
+// AES256GCM is the 256-bit key AES cipher in GCM mode.
+var AES256GCM = cipherWithBlock{
+ ivSize: aes.BlockSize,
+ keySize: 32,
+ newBlock: aes.NewCipher,
+ oid: oidAES256GCM,
+}
diff --git a/vendor/github.com/youmark/pkcs8/kdf_pbkdf2.go b/vendor/github.com/youmark/pkcs8/kdf_pbkdf2.go
new file mode 100644
index 0000000..79697dd
--- /dev/null
+++ b/vendor/github.com/youmark/pkcs8/kdf_pbkdf2.go
@@ -0,0 +1,91 @@
+package pkcs8
+
+import (
+ "crypto"
+ "crypto/sha1"
+ "crypto/sha256"
+ "crypto/x509/pkix"
+ "encoding/asn1"
+ "errors"
+ "hash"
+
+ "golang.org/x/crypto/pbkdf2"
+)
+
+var (
+ oidPKCS5PBKDF2 = asn1.ObjectIdentifier{1, 2, 840, 113549, 1, 5, 12}
+ oidHMACWithSHA1 = asn1.ObjectIdentifier{1, 2, 840, 113549, 2, 7}
+ oidHMACWithSHA256 = asn1.ObjectIdentifier{1, 2, 840, 113549, 2, 9}
+)
+
+func init() {
+ RegisterKDF(oidPKCS5PBKDF2, func() KDFParameters {
+ return new(pbkdf2Params)
+ })
+}
+
+func newHashFromPRF(ai pkix.AlgorithmIdentifier) (func() hash.Hash, error) {
+ switch {
+ case len(ai.Algorithm) == 0 || ai.Algorithm.Equal(oidHMACWithSHA1):
+ return sha1.New, nil
+ case ai.Algorithm.Equal(oidHMACWithSHA256):
+ return sha256.New, nil
+ default:
+ return nil, errors.New("pkcs8: unsupported hash function")
+ }
+}
+
+func newPRFParamFromHash(h crypto.Hash) (pkix.AlgorithmIdentifier, error) {
+ switch h {
+ case crypto.SHA1:
+ return pkix.AlgorithmIdentifier{
+ Algorithm: oidHMACWithSHA1,
+ Parameters: asn1.RawValue{Tag: asn1.TagNull}}, nil
+ case crypto.SHA256:
+ return pkix.AlgorithmIdentifier{
+ Algorithm: oidHMACWithSHA256,
+ Parameters: asn1.RawValue{Tag: asn1.TagNull}}, nil
+ }
+ return pkix.AlgorithmIdentifier{}, errors.New("pkcs8: unsupported hash function")
+}
+
+type pbkdf2Params struct {
+ Salt []byte
+ IterationCount int
+ PRF pkix.AlgorithmIdentifier `asn1:"optional"`
+}
+
+func (p pbkdf2Params) DeriveKey(password []byte, size int) (key []byte, err error) {
+ h, err := newHashFromPRF(p.PRF)
+ if err != nil {
+ return nil, err
+ }
+ return pbkdf2.Key(password, p.Salt, p.IterationCount, size, h), nil
+}
+
+// PBKDF2Opts contains options for the PBKDF2 key derivation function.
+type PBKDF2Opts struct {
+ SaltSize int
+ IterationCount int
+ HMACHash crypto.Hash
+}
+
+func (p PBKDF2Opts) DeriveKey(password, salt []byte, size int) (
+ key []byte, params KDFParameters, err error) {
+
+ key = pbkdf2.Key(password, salt, p.IterationCount, size, p.HMACHash.New)
+ prfParam, err := newPRFParamFromHash(p.HMACHash)
+ if err != nil {
+ return nil, nil, err
+ }
+ params = pbkdf2Params{salt, p.IterationCount, prfParam}
+ return key, params, nil
+}
+
+func (p PBKDF2Opts) GetSaltSize() int {
+ return p.SaltSize
+}
+
+func (p PBKDF2Opts) OID() asn1.ObjectIdentifier {
+ return oidPKCS5PBKDF2
+}
diff --git a/vendor/github.com/youmark/pkcs8/kdf_scrypt.go b/vendor/github.com/youmark/pkcs8/kdf_scrypt.go
new file mode 100644
index 0000000..36c4f4f
--- /dev/null
+++ b/vendor/github.com/youmark/pkcs8/kdf_scrypt.go
@@ -0,0 +1,62 @@
+package pkcs8
+
+import (
+ "encoding/asn1"
+
+ "golang.org/x/crypto/scrypt"
+)
+
+var (
+ oidScrypt = asn1.ObjectIdentifier{1, 3, 6, 1, 4, 1, 11591, 4, 11}
+)
+
+func init() {
+ RegisterKDF(oidScrypt, func() KDFParameters {
+ return new(scryptParams)
+ })
+}
+
+type scryptParams struct {
+ Salt []byte
+ CostParameter int
+ BlockSize int
+ ParallelizationParameter int
+}
+
+func (p scryptParams) DeriveKey(password []byte, size int) (key []byte, err error) {
+ return scrypt.Key(password, p.Salt, p.CostParameter, p.BlockSize,
+ p.ParallelizationParameter, size)
+}
+
+// ScryptOpts contains options for the scrypt key derivation function.
+type ScryptOpts struct {
+ SaltSize int
+ CostParameter int
+ BlockSize int
+ ParallelizationParameter int
+}
+
+func (p ScryptOpts) DeriveKey(password, salt []byte, size int) (
+ key []byte, params KDFParameters, err error) {
+
+ key, err = scrypt.Key(password, salt, p.CostParameter, p.BlockSize,
+ p.ParallelizationParameter, size)
+ if err != nil {
+ return nil, nil, err
+ }
+ params = scryptParams{
+ BlockSize: p.BlockSize,
+ CostParameter: p.CostParameter,
+ ParallelizationParameter: p.ParallelizationParameter,
+ Salt: salt,
+ }
+ return key, params, nil
+}
+
+func (p ScryptOpts) GetSaltSize() int {
+ return p.SaltSize
+}
+
+func (p ScryptOpts) OID() asn1.ObjectIdentifier {
+ return oidScrypt
+}
diff --git a/vendor/github.com/youmark/pkcs8/pkcs8.go b/vendor/github.com/youmark/pkcs8/pkcs8.go
new file mode 100644
index 0000000..f27f627
--- /dev/null
+++ b/vendor/github.com/youmark/pkcs8/pkcs8.go
@@ -0,0 +1,309 @@
+// Package pkcs8 implements functions to parse and convert private keys in PKCS#8 format, as defined in RFC5208 and RFC5958
+package pkcs8
+
+import (
+ "crypto"
+ "crypto/ecdsa"
+ "crypto/rand"
+ "crypto/rsa"
+ "crypto/x509"
+ "crypto/x509/pkix"
+ "encoding/asn1"
+ "errors"
+ "fmt"
+)
+
+// DefaultOpts are the default options for encrypting a key if none are given.
+// The defaults can be changed by the library user.
+var DefaultOpts = &Opts{
+ Cipher: AES256CBC,
+ KDFOpts: PBKDF2Opts{
+ SaltSize: 8,
+ IterationCount: 10000,
+ HMACHash: crypto.SHA256,
+ },
+}
+
+// KDFOpts contains options for a key derivation function.
+// An implementation of this interface must be specified when encrypting a PKCS#8 key.
+type KDFOpts interface {
+ // DeriveKey derives a key of size bytes from the given password and salt.
+ // It returns the key and the ASN.1-encodable parameters used.
+ DeriveKey(password, salt []byte, size int) (key []byte, params KDFParameters, err error)
+ // GetSaltSize returns the salt size specified.
+ GetSaltSize() int
+ // OID returns the OID of the KDF specified.
+ OID() asn1.ObjectIdentifier
+}
+
+// KDFParameters contains parameters (salt, etc.) for a key deriviation function.
+// It must be a ASN.1-decodable structure.
+// An implementation of this interface is created when decoding an encrypted PKCS#8 key.
+type KDFParameters interface {
+ // DeriveKey derives a key of size bytes from the given password.
+ // It uses the salt from the decoded parameters.
+ DeriveKey(password []byte, size int) (key []byte, err error)
+}
+
+var kdfs = make(map[string]func() KDFParameters)
+
+// RegisterKDF registers a function that returns a new instance of the given KDF
+// parameters. This allows the library to support client-provided KDFs.
+func RegisterKDF(oid asn1.ObjectIdentifier, params func() KDFParameters) {
+ kdfs[oid.String()] = params
+}
+
+// Cipher represents a cipher for encrypting the key material.
+type Cipher interface {
+ // IVSize returns the IV size of the cipher, in bytes.
+ IVSize() int
+ // KeySize returns the key size of the cipher, in bytes.
+ KeySize() int
+ // Encrypt encrypts the key material.
+ Encrypt(key, iv, plaintext []byte) ([]byte, error)
+ // Decrypt decrypts the key material.
+ Decrypt(key, iv, ciphertext []byte) ([]byte, error)
+ // OID returns the OID of the cipher specified.
+ OID() asn1.ObjectIdentifier
+}
+
+var ciphers = make(map[string]func() Cipher)
+
+// RegisterCipher registers a function that returns a new instance of the given
+// cipher. This allows the library to support client-provided ciphers.
+func RegisterCipher(oid asn1.ObjectIdentifier, cipher func() Cipher) {
+ ciphers[oid.String()] = cipher
+}
+
+// Opts contains options for encrypting a PKCS#8 key.
+type Opts struct {
+ Cipher Cipher
+ KDFOpts KDFOpts
+}
+
+// Unecrypted PKCS8
+var (
+ oidPBES2 = asn1.ObjectIdentifier{1, 2, 840, 113549, 1, 5, 13}
+)
+
+type encryptedPrivateKeyInfo struct {
+ EncryptionAlgorithm pkix.AlgorithmIdentifier
+ EncryptedData []byte
+}
+
+type pbes2Params struct {
+ KeyDerivationFunc pkix.AlgorithmIdentifier
+ EncryptionScheme pkix.AlgorithmIdentifier
+}
+
+type privateKeyInfo struct {
+ Version int
+ PrivateKeyAlgorithm pkix.AlgorithmIdentifier
+ PrivateKey []byte
+}
+
+func parseKeyDerivationFunc(keyDerivationFunc pkix.AlgorithmIdentifier) (KDFParameters, error) {
+ oid := keyDerivationFunc.Algorithm.String()
+ newParams, ok := kdfs[oid]
+ if !ok {
+ return nil, fmt.Errorf("pkcs8: unsupported KDF (OID: %s)", oid)
+ }
+ params := newParams()
+ _, err := asn1.Unmarshal(keyDerivationFunc.Parameters.FullBytes, params)
+ if err != nil {
+ return nil, errors.New("pkcs8: invalid KDF parameters")
+ }
+ return params, nil
+}
+
+func parseEncryptionScheme(encryptionScheme pkix.AlgorithmIdentifier) (Cipher, []byte, error) {
+ oid := encryptionScheme.Algorithm.String()
+ newCipher, ok := ciphers[oid]
+ if !ok {
+ return nil, nil, fmt.Errorf("pkcs8: unsupported cipher (OID: %s)", oid)
+ }
+ cipher := newCipher()
+ var iv []byte
+ if _, err := asn1.Unmarshal(encryptionScheme.Parameters.FullBytes, &iv); err != nil {
+ return nil, nil, errors.New("pkcs8: invalid cipher parameters")
+ }
+ return cipher, iv, nil
+}
+
+// ParsePrivateKey parses a DER-encoded PKCS#8 private key.
+// Password can be nil.
+// This is equivalent to ParsePKCS8PrivateKey.
+func ParsePrivateKey(der []byte, password []byte) (interface{}, KDFParameters, error) {
+ // No password provided, assume the private key is unencrypted
+ if len(password) == 0 {
+ privateKey, err := x509.ParsePKCS8PrivateKey(der)
+ return privateKey, nil, err
+ }
+
+ // Use the password provided to decrypt the private key
+ var privKey encryptedPrivateKeyInfo
+ if _, err := asn1.Unmarshal(der, &privKey); err != nil {
+ return nil, nil, errors.New("pkcs8: only PKCS #5 v2.0 supported")
+ }
+
+ if !privKey.EncryptionAlgorithm.Algorithm.Equal(oidPBES2) {
+ return nil, nil, errors.New("pkcs8: only PBES2 supported")
+ }
+
+ var params pbes2Params
+ if _, err := asn1.Unmarshal(privKey.EncryptionAlgorithm.Parameters.FullBytes, ¶ms); err != nil {
+ return nil, nil, errors.New("pkcs8: invalid PBES2 parameters")
+ }
+
+ cipher, iv, err := parseEncryptionScheme(params.EncryptionScheme)
+ if err != nil {
+ return nil, nil, err
+ }
+
+ kdfParams, err := parseKeyDerivationFunc(params.KeyDerivationFunc)
+ if err != nil {
+ return nil, nil, err
+ }
+
+ keySize := cipher.KeySize()
+ symkey, err := kdfParams.DeriveKey(password, keySize)
+ if err != nil {
+ return nil, nil, err
+ }
+
+ encryptedKey := privKey.EncryptedData
+ decryptedKey, err := cipher.Decrypt(symkey, iv, encryptedKey)
+ if err != nil {
+ return nil, nil, err
+ }
+
+ key, err := x509.ParsePKCS8PrivateKey(decryptedKey)
+ if err != nil {
+ return nil, nil, errors.New("pkcs8: incorrect password")
+ }
+ return key, kdfParams, nil
+}
+
+// MarshalPrivateKey encodes a private key into DER-encoded PKCS#8 with the given options.
+// Password can be nil.
+func MarshalPrivateKey(priv interface{}, password []byte, opts *Opts) ([]byte, error) {
+ if len(password) == 0 {
+ return x509.MarshalPKCS8PrivateKey(priv)
+ }
+
+ if opts == nil {
+ opts = DefaultOpts
+ }
+
+ // Convert private key into PKCS8 format
+ pkey, err := x509.MarshalPKCS8PrivateKey(priv)
+ if err != nil {
+ return nil, err
+ }
+
+ encAlg := opts.Cipher
+ salt := make([]byte, opts.KDFOpts.GetSaltSize())
+ _, err = rand.Read(salt)
+ if err != nil {
+ return nil, err
+ }
+ iv := make([]byte, encAlg.IVSize())
+ _, err = rand.Read(iv)
+ if err != nil {
+ return nil, err
+ }
+ key, kdfParams, err := opts.KDFOpts.DeriveKey(password, salt, encAlg.KeySize())
+ if err != nil {
+ return nil, err
+ }
+
+ encryptedKey, err := encAlg.Encrypt(key, iv, pkey)
+ if err != nil {
+ return nil, err
+ }
+
+ marshalledParams, err := asn1.Marshal(kdfParams)
+ if err != nil {
+ return nil, err
+ }
+ keyDerivationFunc := pkix.AlgorithmIdentifier{
+ Algorithm: opts.KDFOpts.OID(),
+ Parameters: asn1.RawValue{FullBytes: marshalledParams},
+ }
+ marshalledIV, err := asn1.Marshal(iv)
+ if err != nil {
+ return nil, err
+ }
+ encryptionScheme := pkix.AlgorithmIdentifier{
+ Algorithm: encAlg.OID(),
+ Parameters: asn1.RawValue{FullBytes: marshalledIV},
+ }
+
+ encryptionAlgorithmParams := pbes2Params{
+ EncryptionScheme: encryptionScheme,
+ KeyDerivationFunc: keyDerivationFunc,
+ }
+ marshalledEncryptionAlgorithmParams, err := asn1.Marshal(encryptionAlgorithmParams)
+ if err != nil {
+ return nil, err
+ }
+ encryptionAlgorithm := pkix.AlgorithmIdentifier{
+ Algorithm: oidPBES2,
+ Parameters: asn1.RawValue{FullBytes: marshalledEncryptionAlgorithmParams},
+ }
+
+ encryptedPkey := encryptedPrivateKeyInfo{
+ EncryptionAlgorithm: encryptionAlgorithm,
+ EncryptedData: encryptedKey,
+ }
+
+ return asn1.Marshal(encryptedPkey)
+}
+
+// ParsePKCS8PrivateKey parses encrypted/unencrypted private keys in PKCS#8 format. To parse encrypted private keys, a password of []byte type should be provided to the function as the second parameter.
+func ParsePKCS8PrivateKey(der []byte, v ...[]byte) (interface{}, error) {
+ var password []byte
+ if len(v) > 0 {
+ password = v[0]
+ }
+ privateKey, _, err := ParsePrivateKey(der, password)
+ return privateKey, err
+}
+
+// ParsePKCS8PrivateKeyRSA parses encrypted/unencrypted private keys in PKCS#8 format. To parse encrypted private keys, a password of []byte type should be provided to the function as the second parameter.
+func ParsePKCS8PrivateKeyRSA(der []byte, v ...[]byte) (*rsa.PrivateKey, error) {
+ key, err := ParsePKCS8PrivateKey(der, v...)
+ if err != nil {
+ return nil, err
+ }
+ typedKey, ok := key.(*rsa.PrivateKey)
+ if !ok {
+ return nil, errors.New("key block is not of type RSA")
+ }
+ return typedKey, nil
+}
+
+// ParsePKCS8PrivateKeyECDSA parses encrypted/unencrypted private keys in PKCS#8 format. To parse encrypted private keys, a password of []byte type should be provided to the function as the second parameter.
+func ParsePKCS8PrivateKeyECDSA(der []byte, v ...[]byte) (*ecdsa.PrivateKey, error) {
+ key, err := ParsePKCS8PrivateKey(der, v...)
+ if err != nil {
+ return nil, err
+ }
+ typedKey, ok := key.(*ecdsa.PrivateKey)
+ if !ok {
+ return nil, errors.New("key block is not of type ECDSA")
+ }
+ return typedKey, nil
+}
+
+// ConvertPrivateKeyToPKCS8 converts the private key into PKCS#8 format.
+// To encrypt the private key, the password of []byte type should be provided as the second parameter.
+//
+// The only supported key types are RSA and ECDSA (*rsa.PrivateKey or *ecdsa.PrivateKey for priv)
+func ConvertPrivateKeyToPKCS8(priv interface{}, v ...[]byte) ([]byte, error) {
+ var password []byte
+ if len(v) > 0 {
+ password = v[0]
+ }
+ return MarshalPrivateKey(priv, password, nil)
+}
diff --git a/vendor/go.mongodb.org/mongo-driver/v2/bson/array_codec.go b/vendor/go.mongodb.org/mongo-driver/v2/bson/array_codec.go
index 4642fb6..c369fc4 100644
--- a/vendor/go.mongodb.org/mongo-driver/v2/bson/array_codec.go
+++ b/vendor/go.mongodb.org/mongo-driver/v2/bson/array_codec.go
@@ -7,6 +7,7 @@
package bson
import (
+ "fmt"
"reflect"
"go.mongodb.org/mongo-driver/v2/x/bsonx/bsoncore"
@@ -30,6 +31,17 @@ func (ac *arrayCodec) DecodeValue(_ DecodeContext, vr ValueReader, val reflect.V
if !val.CanSet() || val.Type() != tCoreArray {
return ValueDecoderError{Name: "CoreArrayDecodeValue", Types: []reflect.Type{tCoreArray}, Received: val}
}
+ switch vr.Type() {
+ case TypeArray:
+ case TypeNull:
+ val.Set(reflect.Zero(val.Type()))
+ return vr.ReadNull()
+ case TypeUndefined:
+ val.Set(reflect.Zero(val.Type()))
+ return vr.ReadUndefined()
+ default:
+ return fmt.Errorf("cannot decode %v into a %s", vr.Type(), val.Type())
+ }
if val.IsNil() {
val.Set(reflect.MakeSlice(val.Type(), 0, 0))
diff --git a/vendor/go.mongodb.org/mongo-driver/v2/bson/buffered_byte_src.go b/vendor/go.mongodb.org/mongo-driver/v2/bson/buffered_byte_src.go
index eb19e3c..eb4781c 100644
--- a/vendor/go.mongodb.org/mongo-driver/v2/bson/buffered_byte_src.go
+++ b/vendor/go.mongodb.org/mongo-driver/v2/bson/buffered_byte_src.go
@@ -68,7 +68,7 @@ func (b *bufferedByteSrc) discard(n int) (int, error) {
return n, nil
}
-// readSlice scans buf[offset:] for the first occurrence of delim, returns
+// readSlice reads buf[offset:] for the first occurrence of delim, returning
// buf[offset:idx+1], and advances offset past it; errors if delim not found.
func (b *bufferedByteSrc) readSlice(delim byte) ([]byte, error) {
// Ensure we don't read past the end of the buffer.
diff --git a/vendor/go.mongodb.org/mongo-driver/v2/bson/default_value_decoders.go b/vendor/go.mongodb.org/mongo-driver/v2/bson/default_value_decoders.go
index 8ce5954..91c8409 100644
--- a/vendor/go.mongodb.org/mongo-driver/v2/bson/default_value_decoders.go
+++ b/vendor/go.mongodb.org/mongo-driver/v2/bson/default_value_decoders.go
@@ -119,6 +119,9 @@ func dDecodeValue(dc DecodeContext, vr ValueReader, val reflect.Value) error {
case TypeNull:
val.Set(reflect.Zero(val.Type()))
return vr.ReadNull()
+ case TypeUndefined:
+ val.Set(reflect.Zero(val.Type()))
+ return vr.ReadUndefined()
default:
return fmt.Errorf("cannot decode %v into a D", vrType)
}
@@ -1310,6 +1313,17 @@ func coreDocumentDecodeValue(_ DecodeContext, vr ValueReader, val reflect.Value)
if !val.CanSet() || val.Type() != tCoreDocument {
return ValueDecoderError{Name: "CoreDocumentDecodeValue", Types: []reflect.Type{tCoreDocument}, Received: val}
}
+ switch vrType := vr.Type(); vrType {
+ case Type(0), TypeEmbeddedDocument, TypeArray:
+ case TypeNull:
+ val.Set(reflect.Zero(val.Type()))
+ return vr.ReadNull()
+ case TypeUndefined:
+ val.Set(reflect.Zero(val.Type()))
+ return vr.ReadUndefined()
+ default:
+ return fmt.Errorf("cannot decode %v into a %s", vrType, val.Type())
+ }
if val.IsNil() {
val.Set(reflect.MakeSlice(val.Type(), 0, 0))
diff --git a/vendor/go.mongodb.org/mongo-driver/v2/bson/primitive_codecs.go b/vendor/go.mongodb.org/mongo-driver/v2/bson/primitive_codecs.go
index 11cb870..1d9b56a 100644
--- a/vendor/go.mongodb.org/mongo-driver/v2/bson/primitive_codecs.go
+++ b/vendor/go.mongodb.org/mongo-driver/v2/bson/primitive_codecs.go
@@ -78,6 +78,17 @@ func rawDecodeValue(_ DecodeContext, vr ValueReader, val reflect.Value) error {
if !val.CanSet() || val.Type() != tRaw {
return ValueDecoderError{Name: "RawDecodeValue", Types: []reflect.Type{tRaw}, Received: val}
}
+ switch vrType := vr.Type(); vrType {
+ case Type(0), TypeEmbeddedDocument, TypeArray:
+ case TypeNull:
+ val.Set(reflect.Zero(val.Type()))
+ return vr.ReadNull()
+ case TypeUndefined:
+ val.Set(reflect.Zero(val.Type()))
+ return vr.ReadUndefined()
+ default:
+ return fmt.Errorf("cannot decode %v into a %s", vrType, val.Type())
+ }
if val.IsNil() {
val.Set(reflect.MakeSlice(val.Type(), 0, 0))
diff --git a/vendor/go.mongodb.org/mongo-driver/v2/bson/streaming_byte_src.go b/vendor/go.mongodb.org/mongo-driver/v2/bson/streaming_byte_src.go
index c9366e9..f326dd2 100644
--- a/vendor/go.mongodb.org/mongo-driver/v2/bson/streaming_byte_src.go
+++ b/vendor/go.mongodb.org/mongo-driver/v2/bson/streaming_byte_src.go
@@ -56,15 +56,43 @@ func (s *streamingByteSrc) discard(n int) (int, error) {
return m, err
}
-// readSlice scans buf[offset:] for the first occurrence of delim, returns
-// buf[offset:idx+1], and advances offset past it; errors if delim not found.
+// readSlice reads until the first occurrence of delim, returning a slice
+// containing the data up to and including the delimiter, and advances offset
+// past it; errors if delim not found.
func (s *streamingByteSrc) readSlice(delim byte) ([]byte, error) {
- data, err := s.br.ReadSlice(delim)
- if err != nil {
- return nil, err
+ var full [][]byte
+ var frag []byte
+ var err error
+ var n int
+
+ for {
+ if l := len(frag); l > 0 {
+ // Make a copy of the fragment to accumulate full buffers.
+ buf := make([]byte, l)
+ copy(buf, frag)
+ full = append(full, buf)
+ }
+ frag, err = s.br.ReadSlice(delim)
+ n += len(frag)
+ if err != bufio.ErrBufferFull {
+ break
+ }
+ }
+ s.offset += int64(n)
+
+ // If ReadSlice is only called once, we can return the fragment directly.
+ if len(full) == 0 {
+ return frag, err
+ }
+
+ // Allocate new buffer to hold the full buffers and the fragment.
+ buf := make([]byte, n)
+ n = 0
+ for i := range full {
+ n += copy(buf[n:], full[i])
}
- s.offset += int64(len(data))
- return data, nil
+ copy(buf[n:], frag)
+ return buf, err
}
// pos returns the current read position in the buffer.
diff --git a/vendor/go.mongodb.org/mongo-driver/v2/internal/csfle/csfle.go b/vendor/go.mongodb.org/mongo-driver/v2/internal/csfle/csfle.go
new file mode 100644
index 0000000..7a91045
--- /dev/null
+++ b/vendor/go.mongodb.org/mongo-driver/v2/internal/csfle/csfle.go
@@ -0,0 +1,40 @@
+// Copyright (C) MongoDB, Inc. 2022-present.
+//
+// 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
+
+package csfle
+
+import (
+ "errors"
+ "fmt"
+
+ "go.mongodb.org/mongo-driver/v2/x/bsonx/bsoncore"
+)
+
+const (
+ EncryptedCacheCollection = "ecc"
+ EncryptedStateCollection = "esc"
+ EncryptedCompactionCollection = "ecoc"
+)
+
+// GetEncryptedStateCollectionName returns the encrypted state collection name associated with dataCollectionName.
+func GetEncryptedStateCollectionName(efBSON bsoncore.Document, dataCollectionName string, stateCollection string) (string, error) {
+ fieldName := stateCollection + "Collection"
+ val, err := efBSON.LookupErr(fieldName)
+ if err != nil {
+ if !errors.Is(err, bsoncore.ErrElementNotFound) {
+ return "", err
+ }
+ // Return default name.
+ defaultName := "enxcol_." + dataCollectionName + "." + stateCollection
+ return defaultName, nil
+ }
+
+ stateCollectionName, ok := val.StringValueOK()
+ if !ok {
+ return "", fmt.Errorf("expected string for '%v', got: %v", fieldName, val.Type)
+ }
+ return stateCollectionName, nil
+}
diff --git a/vendor/go.mongodb.org/mongo-driver/v2/internal/mongoutil/mongoutil.go b/vendor/go.mongodb.org/mongo-driver/v2/internal/mongoutil/mongoutil.go
new file mode 100644
index 0000000..be58d38
--- /dev/null
+++ b/vendor/go.mongodb.org/mongo-driver/v2/internal/mongoutil/mongoutil.go
@@ -0,0 +1,101 @@
+// Copyright (C) MongoDB, Inc. 2024-present.
+//
+// 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
+
+package mongoutil
+
+import (
+ "context"
+ "reflect"
+ "time"
+
+ "go.mongodb.org/mongo-driver/v2/mongo/options"
+)
+
+// NewOptions will functionally merge a slice of mongo.Options in a
+// "last-one-wins" manner, where nil options are ignored.
+func NewOptions[T any](opts ...options.Lister[T]) (*T, error) {
+ args := new(T)
+ for _, opt := range opts {
+ if opt == nil || reflect.ValueOf(opt).IsNil() {
+ // Do nothing if the option is nil or if opt is nil but implicitly cast as
+ // an Options interface by the NewArgsFromOptions function. The latter
+ // case would look something like this:
+ continue
+ }
+
+ for _, setArgs := range opt.List() {
+ if setArgs == nil {
+ continue
+ }
+
+ if err := setArgs(args); err != nil {
+ return nil, err
+ }
+ }
+ }
+ return args, nil
+}
+
+// OptionsLister implements an options.SetterLister object for an arbitrary
+// options type.
+type OptionsLister[T any] struct {
+ Options *T // Arguments to set on the option type
+ Callback func(*T) error // A callback for further modification
+}
+
+// List will re-assign the entire argument option to the Args field
+// defined on opts. If a callback exists, that function will be executed to
+// further modify the arguments.
+func (opts *OptionsLister[T]) List() []func(*T) error {
+ return []func(*T) error{
+ func(args *T) error {
+ if opts.Options != nil {
+ *args = *opts.Options
+ }
+
+ if opts.Callback != nil {
+ return opts.Callback(args)
+ }
+
+ return nil
+ },
+ }
+}
+
+// NewOptionsLister will construct a SetterLister from the provided Options
+// object.
+func NewOptionsLister[T any](args *T, callback func(*T) error) *OptionsLister[T] {
+ return &OptionsLister[T]{Options: args, Callback: callback}
+}
+
+// AuthFromURI will create a Credentials object given the provided URI.
+func AuthFromURI(uri string) (*options.Credential, error) {
+ opts := options.Client().ApplyURI(uri)
+
+ return opts.Auth, nil
+}
+
+// HostsFromURI will parse the hosts in the URI and return them as a slice of
+// strings.
+func HostsFromURI(uri string) ([]string, error) {
+ opts := options.Client().ApplyURI(uri)
+
+ return opts.Hosts, nil
+}
+
+// TimeoutWithinContext will return true if the provided timeout is nil or if
+// it is less than the context deadline. If the context does not have a
+// deadline, it will return true.
+func TimeoutWithinContext(ctx context.Context, timeout time.Duration) bool {
+ deadline, ok := ctx.Deadline()
+ if !ok {
+ return true
+ }
+
+ ctxTimeout := time.Until(deadline)
+
+ return ctxTimeout <= 0 || timeout < ctxTimeout
+}
diff --git a/vendor/go.mongodb.org/mongo-driver/v2/internal/optionsutil/options.go b/vendor/go.mongodb.org/mongo-driver/v2/internal/optionsutil/options.go
new file mode 100644
index 0000000..5e7527c
--- /dev/null
+++ b/vendor/go.mongodb.org/mongo-driver/v2/internal/optionsutil/options.go
@@ -0,0 +1,45 @@
+// Copyright (C) MongoDB, Inc. 2025-present.
+//
+// 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
+
+package optionsutil
+
+// Options stores internal options.
+type Options struct {
+ values map[string]any
+}
+
+// WithValue sets an option value with the associated key.
+func WithValue(opts Options, key string, option any) Options {
+ if opts.values == nil {
+ opts.values = make(map[string]any)
+ }
+ opts.values[key] = option
+ return opts
+}
+
+// Value returns the value associated with the options for key.
+func Value(opts Options, key string) any {
+ if opts.values == nil {
+ return nil
+ }
+ if val, ok := opts.values[key]; ok {
+ return val
+ }
+ return nil
+}
+
+// Equal compares two Options instances for equality.
+func Equal(opts1, opts2 Options) bool {
+ if len(opts1.values) != len(opts2.values) {
+ return false
+ }
+ for key, val1 := range opts1.values {
+ if val2, ok := opts2.values[key]; !ok || val1 != val2 {
+ return false
+ }
+ }
+ return true
+}
diff --git a/vendor/go.mongodb.org/mongo-driver/v2/internal/randutil/jitter.go b/vendor/go.mongodb.org/mongo-driver/v2/internal/randutil/jitter.go
new file mode 100644
index 0000000..a546782
--- /dev/null
+++ b/vendor/go.mongodb.org/mongo-driver/v2/internal/randutil/jitter.go
@@ -0,0 +1,35 @@
+// Copyright (C) MongoDB, Inc. 2022-present.
+//
+// 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
+
+package randutil
+
+import "time"
+
+var globalRand = NewLockedRand()
+
+// jitter is the function used by Jitter. Production code uses
+// globalRand.Float64; tests may swap it via SetJitterForTesting.
+var jitter func() float64 = globalRand.Float64
+
+// JitterDuration returns the input duration weighted by a pseudo-random ratio
+// in [0.0, 1.0).
+//
+// If a test jitter function is set by calling SetJitterForTesting,
+// JitterDuration returns the value from that custom function.
+func JitterDuration(d time.Duration) time.Duration {
+ return time.Duration(float64(d) * jitter())
+}
+
+// SetJitterForTesting sets a custom jitter function for testing and returns
+// a restore function. The function should return a ratio in [0.0, 1.0); values
+// outside that range are not validated and will produce backoffs outside
+// the spec-defined range.
+func SetJitterForTesting(f func() float64) func() {
+ jitter = f
+ return func() {
+ jitter = globalRand.Float64
+ }
+}
diff --git a/vendor/go.mongodb.org/mongo-driver/v2/internal/serverselector/server_selector.go b/vendor/go.mongodb.org/mongo-driver/v2/internal/serverselector/server_selector.go
index 86b3373..81f0647 100644
--- a/vendor/go.mongodb.org/mongo-driver/v2/internal/serverselector/server_selector.go
+++ b/vendor/go.mongodb.org/mongo-driver/v2/internal/serverselector/server_selector.go
@@ -11,6 +11,7 @@ import (
"math"
"time"
+ "go.mongodb.org/mongo-driver/v2/mongo/address"
"go.mongodb.org/mongo-driver/v2/mongo/readpref"
"go.mongodb.org/mongo-driver/v2/tag"
"go.mongodb.org/mongo-driver/v2/x/mongo/driver/description"
@@ -178,6 +179,59 @@ func (selector *Write) SelectServer(
}
}
+// Deprioritized filters out deprioritized servers from candidates.
+// If all candidates are deprioritized, returns all candidates as fallback.
+type Deprioritized struct {
+ deprioritizedServers []description.Server
+ innerSelector description.ServerSelector
+}
+
+var _ description.ServerSelector = &Deprioritized{}
+
+// SelectServer filters out deprioritized servers from candidates.
+func (d *Deprioritized) SelectServer(
+ topo description.Topology,
+ candidates []description.Server,
+) ([]description.Server, error) {
+ if len(d.deprioritizedServers) == 0 {
+ return d.innerSelector.SelectServer(topo, candidates)
+ }
+
+ deprioritizedAddrs := make(map[address.Address]struct{})
+ for _, srv := range d.deprioritizedServers {
+ deprioritizedAddrs[srv.Addr] = struct{}{}
+ }
+
+ allowed := []description.Server{}
+
+ // Iterate over the candidates and append them to the allowed slice if
+ // they are not in the deprioritizedServers list.
+ for _, candidate := range candidates {
+ if _, ok := deprioritizedAddrs[candidate.Addr]; !ok {
+ allowed = append(allowed, candidate)
+ }
+ }
+
+ if len(allowed) > 0 {
+ result, err := d.innerSelector.SelectServer(topo, allowed)
+ if err != nil {
+ return nil, err
+ }
+ if len(result) > 0 {
+ return result, nil
+ }
+ }
+ return d.innerSelector.SelectServer(topo, candidates)
+}
+
+// NewDeprioritized wraps an inner selector to filter out deprioritized servers.
+func NewDeprioritized(inner description.ServerSelector, deprioritized []description.Server) description.ServerSelector {
+ return &Deprioritized{
+ deprioritizedServers: deprioritized,
+ innerSelector: inner,
+ }
+}
+
// Func is a function that can be used as a ServerSelector.
type Func func(description.Topology, []description.Server) ([]description.Server, error)
diff --git a/vendor/go.mongodb.org/mongo-driver/v2/mongo/background_context.go b/vendor/go.mongodb.org/mongo-driver/v2/mongo/background_context.go
new file mode 100644
index 0000000..0fac086
--- /dev/null
+++ b/vendor/go.mongodb.org/mongo-driver/v2/mongo/background_context.go
@@ -0,0 +1,34 @@
+// Copyright (C) MongoDB, Inc. 2017-present.
+//
+// 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
+
+package mongo
+
+import "context"
+
+// backgroundContext is an implementation of the context.Context interface that wraps a child Context. Value requests
+// are forwarded to the child Context but the Done and Err functions are overridden to ensure the new context does not
+// time out or get cancelled.
+type backgroundContext struct {
+ context.Context
+ childValuesCtx context.Context
+}
+
+// newBackgroundContext creates a new Context whose behavior matches that of context.Background(), but Value calls are
+// forwarded to the provided ctx parameter. If ctx is nil, context.Background() is returned.
+func newBackgroundContext(ctx context.Context) context.Context {
+ if ctx == nil {
+ return context.Background()
+ }
+
+ return &backgroundContext{
+ Context: context.Background(),
+ childValuesCtx: ctx,
+ }
+}
+
+func (b *backgroundContext) Value(key any) any {
+ return b.childValuesCtx.Value(key)
+}
diff --git a/vendor/go.mongodb.org/mongo-driver/v2/mongo/batch_cursor.go b/vendor/go.mongodb.org/mongo-driver/v2/mongo/batch_cursor.go
new file mode 100644
index 0000000..fc6f760
--- /dev/null
+++ b/vendor/go.mongodb.org/mongo-driver/v2/mongo/batch_cursor.go
@@ -0,0 +1,70 @@
+// Copyright (C) MongoDB, Inc. 2022-present.
+//
+// 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
+
+package mongo
+
+import (
+ "context"
+ "time"
+
+ "go.mongodb.org/mongo-driver/v2/x/bsonx/bsoncore"
+ "go.mongodb.org/mongo-driver/v2/x/mongo/driver"
+)
+
+// batchCursor is the interface implemented by types that can provide batches of document results.
+// The Cursor type is built on top of this type.
+type batchCursor interface {
+ // ID returns the ID of the cursor.
+ ID() int64
+
+ // Next returns true if there is a batch available.
+ Next(context.Context) bool
+
+ // Batch will return a DocumentSequence for the current batch of documents. The returned
+ // DocumentSequence is only valid until the next call to Next or Close.
+ Batch() *bsoncore.Iterator
+
+ // Server returns a pointer to the cursor's server.
+ Server() driver.Server
+
+ // Err returns the last error encountered.
+ Err() error
+
+ // Close closes the cursor.
+ Close(context.Context) error
+
+ // SetBatchSize is a modifier function used to adjust the batch size of
+ // the cursor that implements it.
+ SetBatchSize(int32)
+
+ // SetMaxAwaitTime will set the maximum amount of time the server will allow
+ // the operations to execute. The server will error if this field is set
+ // but the cursor is not configured with awaitData=true.
+ //
+ // The time.Duration value passed by this setter will be converted and
+ // rounded down to the nearest millisecond.
+ SetMaxAwaitTime(time.Duration)
+
+ // SetComment will set a user-configurable comment that can be used to
+ // identify the operation in server logs.
+ SetComment(any)
+
+ // MaxAwaitTime returns the maximum amount of time the server will allow
+ // the operations to execute. This is only valid for tailable awaitData
+ // cursors.
+ MaxAwaitTime() *time.Duration
+}
+
+// changeStreamCursor is the interface implemented by batch cursors that also provide the functionality for retrieving
+// a postBatchResumeToken from commands and allows for the cursor to be killed rather than closed
+type changeStreamCursor interface {
+ batchCursor
+ // PostBatchResumeToken returns the latest seen post batch resume token.
+ PostBatchResumeToken() bsoncore.Document
+
+ // KillCursor kills cursor on server without closing batch cursor
+ KillCursor(context.Context) error
+}
diff --git a/vendor/go.mongodb.org/mongo-driver/v2/mongo/bulk_write.go b/vendor/go.mongodb.org/mongo-driver/v2/mongo/bulk_write.go
new file mode 100644
index 0000000..1065cdf
--- /dev/null
+++ b/vendor/go.mongodb.org/mongo-driver/v2/mongo/bulk_write.go
@@ -0,0 +1,652 @@
+// Copyright (C) MongoDB, Inc. 2017-present.
+//
+// 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
+
+package mongo
+
+import (
+ "context"
+ "errors"
+ "fmt"
+
+ "go.mongodb.org/mongo-driver/v2/bson"
+ "go.mongodb.org/mongo-driver/v2/mongo/options"
+ "go.mongodb.org/mongo-driver/v2/mongo/writeconcern"
+ "go.mongodb.org/mongo-driver/v2/x/bsonx/bsoncore"
+ "go.mongodb.org/mongo-driver/v2/x/mongo/driver"
+ "go.mongodb.org/mongo-driver/v2/x/mongo/driver/description"
+ "go.mongodb.org/mongo-driver/v2/x/mongo/driver/operation"
+ "go.mongodb.org/mongo-driver/v2/x/mongo/driver/session"
+)
+
+type bulkWriteBatch struct {
+ models []WriteModel
+ canRetry bool
+ indexes []int
+}
+
+// bulkWrite performs a bulkwrite operation
+type bulkWrite struct {
+ comment any
+ ordered *bool
+ bypassDocumentValidation *bool
+ models []WriteModel
+ session *session.Client
+ collection *Collection
+ selector description.ServerSelector
+ writeConcern *writeconcern.WriteConcern
+ result BulkWriteResult
+ let any
+ rawData *bool
+ additionalCmd bson.D
+}
+
+func (bw *bulkWrite) execute(ctx context.Context) error {
+ ordered := true
+ if bw.ordered != nil {
+ ordered = *bw.ordered
+ }
+
+ batches := createBatches(bw.models, ordered)
+ bw.result = BulkWriteResult{
+ UpsertedIDs: make(map[int64]any),
+ }
+
+ bwErr := BulkWriteException{
+ WriteErrors: make([]BulkWriteError, 0),
+ }
+
+ var lastErr error
+ continueOnError := !ordered
+ for _, batch := range batches {
+ if len(batch.models) == 0 {
+ continue
+ }
+
+ batchRes, batchErr, err := bw.runBatch(ctx, batch)
+
+ bw.mergeResults(batchRes)
+
+ bwErr.WriteConcernError = batchErr.WriteConcernError
+ bwErr.Labels = append(bwErr.Labels, batchErr.Labels...)
+
+ bwErr.WriteErrors = append(bwErr.WriteErrors, batchErr.WriteErrors...)
+
+ commandErrorOccurred := err != nil && !errors.Is(err, driver.ErrUnacknowledgedWrite)
+ writeErrorOccurred := len(batchErr.WriteErrors) > 0 || batchErr.WriteConcernError != nil
+ if !continueOnError && (commandErrorOccurred || writeErrorOccurred) {
+ if err != nil {
+ return err
+ }
+
+ return bwErr
+ }
+
+ if err != nil {
+ lastErr = err
+ }
+ }
+
+ bw.result.MatchedCount -= bw.result.UpsertedCount
+
+ rr, err := processWriteError(lastErr)
+ if err != nil {
+ return err
+ }
+
+ bw.result.Acknowledged = rr.isAcknowledged()
+
+ if len(bwErr.WriteErrors) > 0 || bwErr.WriteConcernError != nil {
+ return bwErr
+ }
+ return nil
+}
+
+func (bw *bulkWrite) runBatch(ctx context.Context, batch bulkWriteBatch) (BulkWriteResult, BulkWriteException, error) {
+ batchRes := BulkWriteResult{
+ UpsertedIDs: make(map[int64]any),
+ }
+ batchErr := BulkWriteException{}
+
+ var writeErrors []driver.WriteError
+ switch batch.models[0].(type) {
+ case *InsertOneModel:
+ res, err := bw.runInsert(ctx, batch)
+ if err != nil {
+ var writeErr driver.WriteCommandError
+ if !errors.As(err, &writeErr) {
+ return BulkWriteResult{}, batchErr, err
+ }
+ writeErrors = writeErr.WriteErrors
+ batchErr.Labels = writeErr.Labels
+ batchErr.WriteConcernError = convertDriverWriteConcernError(writeErr.WriteConcernError)
+ }
+ batchRes.InsertedCount = res.N
+ case *DeleteOneModel, *DeleteManyModel:
+ res, err := bw.runDelete(ctx, batch)
+ if err != nil {
+ var writeErr driver.WriteCommandError
+ if !errors.As(err, &writeErr) {
+ return BulkWriteResult{}, batchErr, err
+ }
+ writeErrors = writeErr.WriteErrors
+ batchErr.Labels = writeErr.Labels
+ batchErr.WriteConcernError = convertDriverWriteConcernError(writeErr.WriteConcernError)
+ }
+ batchRes.DeletedCount = res.N
+ case *ReplaceOneModel, *UpdateOneModel, *UpdateManyModel:
+ res, err := bw.runUpdate(ctx, batch)
+ if err != nil {
+ var writeErr driver.WriteCommandError
+ if !errors.As(err, &writeErr) {
+ return BulkWriteResult{}, batchErr, err
+ }
+ writeErrors = writeErr.WriteErrors
+ batchErr.Labels = writeErr.Labels
+ batchErr.WriteConcernError = convertDriverWriteConcernError(writeErr.WriteConcernError)
+ }
+ batchRes.MatchedCount = res.N
+ batchRes.ModifiedCount = res.NModified
+ batchRes.UpsertedCount = int64(len(res.Upserted))
+ for _, upsert := range res.Upserted {
+ batchRes.UpsertedIDs[int64(batch.indexes[upsert.Index])] = upsert.ID
+ }
+ }
+
+ batchErr.WriteErrors = make([]BulkWriteError, 0, len(writeErrors))
+ convWriteErrors := writeErrorsFromDriverWriteErrors(writeErrors)
+ for _, we := range convWriteErrors {
+ request := batch.models[we.Index]
+ we.Index = batch.indexes[we.Index]
+ batchErr.WriteErrors = append(batchErr.WriteErrors, BulkWriteError{
+ WriteError: we,
+ Request: request,
+ })
+ }
+ return batchRes, batchErr, nil
+}
+
+func (bw *bulkWrite) runInsert(ctx context.Context, batch bulkWriteBatch) (insertResult, error) {
+ docs := make([]bsoncore.Document, len(batch.models))
+ for i, model := range batch.models {
+ converted := model.(*InsertOneModel)
+ doc, err := marshal(converted.Document, bw.collection.bsonOpts, bw.collection.registry)
+ if err != nil {
+ return insertResult{}, err
+ }
+ doc, _, err = ensureID(doc, bson.NilObjectID, bw.collection.bsonOpts, bw.collection.registry)
+ if err != nil {
+ return insertResult{}, err
+ }
+
+ docs[i] = doc
+ }
+
+ maxAdaptiveRetries := bw.collection.client.effectiveAdaptiveRetries(bw.collection.client.retryWrites)
+
+ op := insert{
+ documents: docs,
+ session: bw.session,
+ writeConcern: bw.writeConcern,
+ monitor: bw.collection.client.monitor,
+ selector: bw.selector,
+ clock: bw.collection.client.clock,
+ database: bw.collection.db.name,
+ collection: bw.collection.name,
+ deployment: bw.collection.client.deployment,
+ crypt: bw.collection.client.cryptFLE,
+ serverAPI: bw.collection.client.serverAPI,
+ timeout: bw.collection.client.timeout,
+ logger: bw.collection.client.logger,
+ authenticator: bw.collection.client.authenticator,
+
+ maxAdaptiveRetries: maxAdaptiveRetries,
+ enableOverloadRetargeting: bw.collection.client.enableOverloadRetargeting,
+ }
+
+ if bw.comment != nil {
+ comment, err := marshalValue(bw.comment, bw.collection.bsonOpts, bw.collection.registry)
+ if err != nil {
+ return op.Result(), err
+ }
+ op.comment = comment
+ }
+ if bw.bypassDocumentValidation != nil && *bw.bypassDocumentValidation {
+ op.bypassDocumentValidation = bw.bypassDocumentValidation
+ }
+ if bw.ordered != nil {
+ op.ordered = bw.ordered
+ }
+
+ retry := driver.RetryNone
+ if bw.collection.client.retryWrites && batch.canRetry {
+ retry = driver.RetryOncePerCommand
+ }
+ op.retry = &retry
+
+ if bw.rawData != nil {
+ op.rawData = bw.rawData
+ }
+ if len(bw.additionalCmd) > 0 {
+ op.additionalCmd = bw.additionalCmd
+ }
+
+ err := op.Execute(ctx)
+
+ return op.Result(), err
+}
+
+func (bw *bulkWrite) runDelete(ctx context.Context, batch bulkWriteBatch) (operation.DeleteResult, error) {
+ docs := make([]bsoncore.Document, len(batch.models))
+ var i int
+ var hasHint bool
+
+ for _, model := range batch.models {
+ var doc bsoncore.Document
+ var err error
+
+ switch converted := model.(type) {
+ case *DeleteOneModel:
+ doc, err = createDeleteDoc(
+ converted.Filter,
+ converted.Collation,
+ converted.Hint,
+ true,
+ bw.collection.bsonOpts,
+ bw.collection.registry)
+ hasHint = hasHint || (converted.Hint != nil)
+ case *DeleteManyModel:
+ doc, err = createDeleteDoc(
+ converted.Filter,
+ converted.Collation,
+ converted.Hint,
+ false,
+ bw.collection.bsonOpts,
+ bw.collection.registry)
+ hasHint = hasHint || (converted.Hint != nil)
+ }
+
+ if err != nil {
+ return operation.DeleteResult{}, err
+ }
+
+ docs[i] = doc
+ i++
+ }
+
+ retry := driver.RetryNone
+ if bw.collection.client.retryWrites && batch.canRetry {
+ retry = driver.RetryOncePerCommand
+ }
+
+ maxAdaptiveRetries := bw.collection.client.effectiveAdaptiveRetries(bw.collection.client.retryWrites)
+
+ op := operation.NewDelete(docs...).
+ Session(bw.session).WriteConcern(bw.writeConcern).CommandMonitor(bw.collection.client.monitor).
+ ServerSelector(bw.selector).ClusterClock(bw.collection.client.clock).
+ Database(bw.collection.db.name).Collection(bw.collection.name).
+ Retry(retry).MaxAdaptiveRetries(maxAdaptiveRetries).
+ EnableOverloadRetargeting(bw.collection.client.enableOverloadRetargeting).
+ Deployment(bw.collection.client.deployment).Crypt(bw.collection.client.cryptFLE).Hint(hasHint).
+ ServerAPI(bw.collection.client.serverAPI).Timeout(bw.collection.client.timeout).
+ Logger(bw.collection.client.logger).Authenticator(bw.collection.client.authenticator)
+ if bw.comment != nil {
+ comment, err := marshalValue(bw.comment, bw.collection.bsonOpts, bw.collection.registry)
+ if err != nil {
+ return op.Result(), err
+ }
+ op.Comment(comment)
+ }
+ if bw.let != nil {
+ let, err := marshal(bw.let, bw.collection.bsonOpts, bw.collection.registry)
+ if err != nil {
+ return operation.DeleteResult{}, err
+ }
+ op = op.Let(let)
+ }
+ if bw.ordered != nil {
+ op = op.Ordered(*bw.ordered)
+ }
+
+ if bw.rawData != nil {
+ op.RawData(*bw.rawData)
+ }
+
+ err := op.Execute(ctx)
+
+ return op.Result(), err
+}
+
+func createDeleteDoc(
+ filter any,
+ collation *options.Collation,
+ hint any,
+ deleteOne bool,
+ bsonOpts *options.BSONOptions,
+ registry *bson.Registry,
+) (bsoncore.Document, error) {
+ if filter == nil {
+ return nil, fmt.Errorf("delete filter cannot be nil")
+ }
+ f, err := marshal(filter, bsonOpts, registry)
+ if err != nil {
+ return nil, err
+ }
+
+ var limit int32
+ if deleteOne {
+ limit = 1
+ }
+ didx, doc := bsoncore.AppendDocumentStart(nil)
+ doc = bsoncore.AppendDocumentElement(doc, "q", f)
+ doc = bsoncore.AppendInt32Element(doc, "limit", limit)
+ if collation != nil {
+ doc = bsoncore.AppendDocumentElement(doc, "collation", toDocument(collation))
+ }
+ if hint != nil {
+ if isUnorderedMap(hint) {
+ return nil, ErrMapForOrderedArgument{"hint"}
+ }
+ hintVal, err := marshalValue(hint, bsonOpts, registry)
+ if err != nil {
+ return nil, err
+ }
+ doc = bsoncore.AppendValueElement(doc, "hint", hintVal)
+ }
+ doc, _ = bsoncore.AppendDocumentEnd(doc, didx)
+
+ return doc, nil
+}
+
+func (bw *bulkWrite) runUpdate(ctx context.Context, batch bulkWriteBatch) (operation.UpdateResult, error) {
+ docs := make([]bsoncore.Document, len(batch.models))
+ var hasHint bool
+ var hasArrayFilters bool
+ for i, model := range batch.models {
+ var doc bsoncore.Document
+ var err error
+
+ switch converted := model.(type) {
+ case *ReplaceOneModel:
+ doc, err = updateDoc{
+ filter: converted.Filter,
+ update: converted.Replacement,
+ hint: converted.Hint,
+ sort: converted.Sort,
+ collation: converted.Collation,
+ upsert: converted.Upsert,
+ }.marshal(bw.collection.bsonOpts, bw.collection.registry)
+ hasHint = hasHint || (converted.Hint != nil)
+ case *UpdateOneModel:
+ doc, err = updateDoc{
+ filter: converted.Filter,
+ update: converted.Update,
+ hint: converted.Hint,
+ sort: converted.Sort,
+ arrayFilters: converted.ArrayFilters,
+ collation: converted.Collation,
+ upsert: converted.Upsert,
+ checkDollarKey: true,
+ }.marshal(bw.collection.bsonOpts, bw.collection.registry)
+ hasHint = hasHint || (converted.Hint != nil)
+ hasArrayFilters = hasArrayFilters || (converted.ArrayFilters != nil)
+ case *UpdateManyModel:
+ doc, err = updateDoc{
+ filter: converted.Filter,
+ update: converted.Update,
+ hint: converted.Hint,
+ arrayFilters: converted.ArrayFilters,
+ collation: converted.Collation,
+ upsert: converted.Upsert,
+ multi: true,
+ checkDollarKey: true,
+ }.marshal(bw.collection.bsonOpts, bw.collection.registry)
+ hasHint = hasHint || (converted.Hint != nil)
+ hasArrayFilters = hasArrayFilters || (converted.ArrayFilters != nil)
+ }
+ if err != nil {
+ return operation.UpdateResult{}, err
+ }
+
+ docs[i] = doc
+ }
+
+ retry := driver.RetryNone
+ if bw.collection.client.retryWrites && batch.canRetry {
+ retry = driver.RetryOncePerCommand
+ }
+
+ maxAdaptiveRetries := bw.collection.client.effectiveAdaptiveRetries(bw.collection.client.retryWrites)
+
+ op := operation.NewUpdate(docs...).
+ Session(bw.session).WriteConcern(bw.writeConcern).CommandMonitor(bw.collection.client.monitor).
+ ServerSelector(bw.selector).ClusterClock(bw.collection.client.clock).
+ Database(bw.collection.db.name).Collection(bw.collection.name).
+ Retry(retry).MaxAdaptiveRetries(maxAdaptiveRetries).
+ EnableOverloadRetargeting(bw.collection.client.enableOverloadRetargeting).
+ Deployment(bw.collection.client.deployment).Crypt(bw.collection.client.cryptFLE).Hint(hasHint).
+ ArrayFilters(hasArrayFilters).ServerAPI(bw.collection.client.serverAPI).
+ Timeout(bw.collection.client.timeout).Logger(bw.collection.client.logger).
+ Authenticator(bw.collection.client.authenticator)
+ if bw.comment != nil {
+ comment, err := marshalValue(bw.comment, bw.collection.bsonOpts, bw.collection.registry)
+ if err != nil {
+ return op.Result(), err
+ }
+ op.Comment(comment)
+ }
+ if bw.let != nil {
+ let, err := marshal(bw.let, bw.collection.bsonOpts, bw.collection.registry)
+ if err != nil {
+ return operation.UpdateResult{}, err
+ }
+ op = op.Let(let)
+ }
+ if bw.ordered != nil {
+ op = op.Ordered(*bw.ordered)
+ }
+ if bw.bypassDocumentValidation != nil && *bw.bypassDocumentValidation {
+ op = op.BypassDocumentValidation(*bw.bypassDocumentValidation)
+ }
+
+ if bw.rawData != nil {
+ op.RawData(*bw.rawData)
+ }
+ if len(bw.additionalCmd) > 0 {
+ op.AdditionalCmd(bw.additionalCmd)
+ }
+
+ err := op.Execute(ctx)
+
+ return op.Result(), err
+}
+
+type updateDoc struct {
+ filter any
+ update any
+ hint any
+ sort any
+ arrayFilters []any
+ collation *options.Collation
+ upsert *bool
+ multi bool
+ checkDollarKey bool
+}
+
+func (doc updateDoc) marshal(bsonOpts *options.BSONOptions, registry *bson.Registry) (bsoncore.Document, error) {
+ if doc.filter == nil {
+ return nil, fmt.Errorf("update filter cannot be nil")
+ }
+ f, err := marshal(doc.filter, bsonOpts, registry)
+ if err != nil {
+ return nil, err
+ }
+
+ uidx, updateDoc := bsoncore.AppendDocumentStart(nil)
+ updateDoc = bsoncore.AppendDocumentElement(updateDoc, "q", f)
+
+ u, err := marshalUpdateValue(doc.update, bsonOpts, registry, doc.checkDollarKey)
+ if err != nil {
+ return nil, err
+ }
+
+ updateDoc = bsoncore.AppendValueElement(updateDoc, "u", u)
+
+ if doc.multi {
+ updateDoc = bsoncore.AppendBooleanElement(updateDoc, "multi", doc.multi)
+ }
+ if doc.sort != nil {
+ if isUnorderedMap(doc.sort) {
+ return nil, ErrMapForOrderedArgument{"sort"}
+ }
+ s, err := marshal(doc.sort, bsonOpts, registry)
+ if err != nil {
+ return nil, err
+ }
+ updateDoc = bsoncore.AppendDocumentElement(updateDoc, "sort", s)
+ }
+
+ if doc.arrayFilters != nil {
+ reg := registry
+ arr, err := marshalValue(doc.arrayFilters, bsonOpts, reg)
+ if err != nil {
+ return nil, err
+ }
+ updateDoc = bsoncore.AppendArrayElement(updateDoc, "arrayFilters", arr.Data)
+ }
+
+ if doc.collation != nil {
+ updateDoc = bsoncore.AppendDocumentElement(updateDoc, "collation", bsoncore.Document(toDocument(doc.collation)))
+ }
+
+ if doc.upsert != nil {
+ updateDoc = bsoncore.AppendBooleanElement(updateDoc, "upsert", *doc.upsert)
+ }
+
+ if doc.hint != nil {
+ if isUnorderedMap(doc.hint) {
+ return nil, ErrMapForOrderedArgument{"hint"}
+ }
+ hintVal, err := marshalValue(doc.hint, bsonOpts, registry)
+ if err != nil {
+ return nil, err
+ }
+ updateDoc = bsoncore.AppendValueElement(updateDoc, "hint", hintVal)
+ }
+
+ updateDoc, _ = bsoncore.AppendDocumentEnd(updateDoc, uidx)
+ return updateDoc, nil
+}
+
+func createBatches(models []WriteModel, ordered bool) []bulkWriteBatch {
+ if ordered {
+ return createOrderedBatches(models)
+ }
+
+ batches := make([]bulkWriteBatch, 5)
+ batches[insertCommand].canRetry = true
+ batches[deleteOneCommand].canRetry = true
+ batches[updateOneCommand].canRetry = true
+
+ // TODO(GODRIVER-1157): fix batching once operation retryability is fixed
+ for i, model := range models {
+ switch model.(type) {
+ case *InsertOneModel:
+ batches[insertCommand].models = append(batches[insertCommand].models, model)
+ batches[insertCommand].indexes = append(batches[insertCommand].indexes, i)
+ case *DeleteOneModel:
+ batches[deleteOneCommand].models = append(batches[deleteOneCommand].models, model)
+ batches[deleteOneCommand].indexes = append(batches[deleteOneCommand].indexes, i)
+ case *DeleteManyModel:
+ batches[deleteManyCommand].models = append(batches[deleteManyCommand].models, model)
+ batches[deleteManyCommand].indexes = append(batches[deleteManyCommand].indexes, i)
+ case *ReplaceOneModel, *UpdateOneModel:
+ batches[updateOneCommand].models = append(batches[updateOneCommand].models, model)
+ batches[updateOneCommand].indexes = append(batches[updateOneCommand].indexes, i)
+ case *UpdateManyModel:
+ batches[updateManyCommand].models = append(batches[updateManyCommand].models, model)
+ batches[updateManyCommand].indexes = append(batches[updateManyCommand].indexes, i)
+ }
+ }
+
+ return batches
+}
+
+func createOrderedBatches(models []WriteModel) []bulkWriteBatch {
+ var batches []bulkWriteBatch
+ var prevKind writeCommandKind = -1
+ i := -1 // batch index
+
+ for ind, model := range models {
+ var createNewBatch bool
+ var canRetry bool
+ var newKind writeCommandKind
+
+ // TODO(GODRIVER-1157): fix batching once operation retryability is fixed
+ switch model.(type) {
+ case *InsertOneModel:
+ createNewBatch = prevKind != insertCommand
+ canRetry = true
+ newKind = insertCommand
+ case *DeleteOneModel:
+ createNewBatch = prevKind != deleteOneCommand
+ canRetry = true
+ newKind = deleteOneCommand
+ case *DeleteManyModel:
+ createNewBatch = prevKind != deleteManyCommand
+ newKind = deleteManyCommand
+ case *ReplaceOneModel, *UpdateOneModel:
+ createNewBatch = prevKind != updateOneCommand
+ canRetry = true
+ newKind = updateOneCommand
+ case *UpdateManyModel:
+ createNewBatch = prevKind != updateManyCommand
+ newKind = updateManyCommand
+ }
+
+ if createNewBatch {
+ batches = append(batches, bulkWriteBatch{
+ models: []WriteModel{model},
+ canRetry: canRetry,
+ indexes: []int{ind},
+ })
+ i++
+ } else {
+ batches[i].models = append(batches[i].models, model)
+ if !canRetry {
+ batches[i].canRetry = false // don't make it true if it was already false
+ }
+ batches[i].indexes = append(batches[i].indexes, ind)
+ }
+
+ prevKind = newKind
+ }
+
+ return batches
+}
+
+func (bw *bulkWrite) mergeResults(newResult BulkWriteResult) {
+ bw.result.InsertedCount += newResult.InsertedCount
+ bw.result.MatchedCount += newResult.MatchedCount
+ bw.result.ModifiedCount += newResult.ModifiedCount
+ bw.result.DeletedCount += newResult.DeletedCount
+ bw.result.UpsertedCount += newResult.UpsertedCount
+
+ for index, upsertID := range newResult.UpsertedIDs {
+ bw.result.UpsertedIDs[index] = upsertID
+ }
+}
+
+// WriteCommandKind is the type of command represented by a Write
+type writeCommandKind int8
+
+// These constants represent the valid types of write commands.
+const (
+ insertCommand writeCommandKind = iota
+ updateOneCommand
+ updateManyCommand
+ deleteOneCommand
+ deleteManyCommand
+)
diff --git a/vendor/go.mongodb.org/mongo-driver/v2/mongo/bulk_write_models.go b/vendor/go.mongodb.org/mongo-driver/v2/mongo/bulk_write_models.go
new file mode 100644
index 0000000..f28af16
--- /dev/null
+++ b/vendor/go.mongodb.org/mongo-driver/v2/mongo/bulk_write_models.go
@@ -0,0 +1,342 @@
+// Copyright (C) MongoDB, Inc. 2017-present.
+//
+// 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
+
+package mongo
+
+import (
+ "go.mongodb.org/mongo-driver/v2/mongo/options"
+)
+
+// WriteModel is an interface implemented by models that can be used in a BulkWrite operation. Each WriteModel
+// represents a write.
+//
+// This interface is implemented by InsertOneModel, DeleteOneModel, DeleteManyModel, ReplaceOneModel, UpdateOneModel,
+// and UpdateManyModel. Custom implementations of this interface must not be used.
+type WriteModel interface {
+ writeModel()
+}
+
+// InsertOneModel is used to insert a single document in a BulkWrite operation.
+//
+// See corresponding setter methods for documentation.
+type InsertOneModel struct {
+ Document any
+}
+
+// NewInsertOneModel creates a new InsertOneModel.
+func NewInsertOneModel() *InsertOneModel {
+ return &InsertOneModel{}
+}
+
+// SetDocument specifies the document to be inserted. The document cannot be nil. If it does not have an _id field when
+// transformed into BSON, one will be added automatically to the marshalled document. The original document will not be
+// modified.
+func (iom *InsertOneModel) SetDocument(doc any) *InsertOneModel {
+ iom.Document = doc
+ return iom
+}
+
+func (*InsertOneModel) writeModel() {}
+
+// DeleteOneModel is used to delete at most one document in a BulkWriteOperation.
+//
+// See corresponding setter methods for documentation.
+type DeleteOneModel struct {
+ Filter any
+ Collation *options.Collation
+ Hint any
+}
+
+// NewDeleteOneModel creates a new DeleteOneModel.
+func NewDeleteOneModel() *DeleteOneModel {
+ return &DeleteOneModel{}
+}
+
+// SetFilter specifies a filter to use to select the document to delete. The filter must be a document containing query
+// operators. It cannot be nil. If the filter matches multiple documents, one will be selected from the matching
+// documents.
+func (dom *DeleteOneModel) SetFilter(filter any) *DeleteOneModel {
+ dom.Filter = filter
+ return dom
+}
+
+// SetCollation specifies a collation to use for string comparisons. The default is nil, meaning no collation will be
+// used.
+func (dom *DeleteOneModel) SetCollation(collation *options.Collation) *DeleteOneModel {
+ dom.Collation = collation
+ return dom
+}
+
+// SetHint specifies the index to use for the operation. This should either be
+// the index name as a string or the index specification as a document. This
+// option is only valid for MongoDB versions >= 4.4. Server versions < 4.4 will
+// return an error if this option is specified. The driver will return an error
+// if this option is specified during an unacknowledged write operation. The
+// driver will return an error if the hint parameter is a multi-key map. The
+// default value is nil, which means that no hint will be sent.
+func (dom *DeleteOneModel) SetHint(hint any) *DeleteOneModel {
+ dom.Hint = hint
+ return dom
+}
+
+func (*DeleteOneModel) writeModel() {}
+
+// DeleteManyModel is used to delete multiple documents in a BulkWrite operation.
+//
+// See corresponding setter methods for documentation.
+type DeleteManyModel struct {
+ Filter any
+ Collation *options.Collation
+ Hint any
+}
+
+// NewDeleteManyModel creates a new DeleteManyModel.
+func NewDeleteManyModel() *DeleteManyModel {
+ return &DeleteManyModel{}
+}
+
+// SetFilter specifies a filter to use to select documents to delete. The filter must be a document containing query
+// operators. It cannot be nil.
+func (dmm *DeleteManyModel) SetFilter(filter any) *DeleteManyModel {
+ dmm.Filter = filter
+ return dmm
+}
+
+// SetCollation specifies a collation to use for string comparisons. The default is nil, meaning no collation will be
+// used.
+func (dmm *DeleteManyModel) SetCollation(collation *options.Collation) *DeleteManyModel {
+ dmm.Collation = collation
+ return dmm
+}
+
+// SetHint specifies the index to use for the operation. This should either be
+// the index name as a string or the index specification as a document. This
+// option is only valid for MongoDB versions >= 4.4. Server versions < 4.4 will
+// return an error if this option is specified. The driver will return an error
+// if this option is specified during an unacknowledged write operation. The
+// driver will return an error if the hint parameter is a multi-key map. The
+// default value is nil, which means that no hint will be sent.
+func (dmm *DeleteManyModel) SetHint(hint any) *DeleteManyModel {
+ dmm.Hint = hint
+ return dmm
+}
+
+func (*DeleteManyModel) writeModel() {}
+
+// ReplaceOneModel is used to replace at most one document in a BulkWrite operation.
+//
+// See corresponding setter methods for documentation.
+type ReplaceOneModel struct {
+ Collation *options.Collation
+ Upsert *bool
+ Filter any
+ Replacement any
+ Hint any
+ Sort any
+}
+
+// NewReplaceOneModel creates a new ReplaceOneModel.
+func NewReplaceOneModel() *ReplaceOneModel {
+ return &ReplaceOneModel{}
+}
+
+// SetHint specifies the index to use for the operation. This should either be
+// the index name as a string or the index specification as a document. This
+// option is only valid for MongoDB versions >= 4.2. Server versions < 4.2 will
+// return an error if this option is specified. The driver will return an error
+// if this option is specified during an unacknowledged write operation. The
+// driver will return an error if the hint parameter is a multi-key map. The
+// default value is nil, which means that no hint will be sent.
+func (rom *ReplaceOneModel) SetHint(hint any) *ReplaceOneModel {
+ rom.Hint = hint
+ return rom
+}
+
+// SetFilter specifies a filter to use to select the document to replace. The filter must be a document containing query
+// operators. It cannot be nil. If the filter matches multiple documents, one will be selected from the matching
+// documents.
+func (rom *ReplaceOneModel) SetFilter(filter any) *ReplaceOneModel {
+ rom.Filter = filter
+ return rom
+}
+
+// SetReplacement specifies a document that will be used to replace the selected document. It cannot be nil and cannot
+// contain any update operators (https://www.mongodb.com/docs/manual/reference/operator/update/).
+func (rom *ReplaceOneModel) SetReplacement(rep any) *ReplaceOneModel {
+ rom.Replacement = rep
+ return rom
+}
+
+// SetCollation specifies a collation to use for string comparisons. The default is nil, meaning no collation will be
+// used.
+func (rom *ReplaceOneModel) SetCollation(collation *options.Collation) *ReplaceOneModel {
+ rom.Collation = collation
+ return rom
+}
+
+// SetUpsert specifies whether or not the replacement document should be inserted if no document matching the filter is
+// found. If an upsert is performed, the _id of the upserted document can be retrieved from the UpsertedIDs field of the
+// BulkWriteResult.
+func (rom *ReplaceOneModel) SetUpsert(upsert bool) *ReplaceOneModel {
+ rom.Upsert = &upsert
+ return rom
+}
+
+// SetSort specifies which document the operation replaces if the query matches multiple documents. The first document
+// matched by the sort order will be replaced. This option is only valid for MongoDB versions >= 8.0. The sort parameter
+// is evaluated sequentially, so the driver will return an error if it is a multi-key map (which is unordeded). The
+// default value is nil.
+func (rom *ReplaceOneModel) SetSort(sort any) *ReplaceOneModel {
+ rom.Sort = sort
+ return rom
+}
+
+func (*ReplaceOneModel) writeModel() {}
+
+// UpdateOneModel is used to update at most one document in a BulkWrite operation.
+//
+// See corresponding setter methods for documentation.
+type UpdateOneModel struct {
+ Collation *options.Collation
+ Upsert *bool
+ Filter any
+ Update any
+ ArrayFilters []any
+ Hint any
+ Sort any
+}
+
+// NewUpdateOneModel creates a new UpdateOneModel.
+func NewUpdateOneModel() *UpdateOneModel {
+ return &UpdateOneModel{}
+}
+
+// SetHint specifies the index to use for the operation. This should either be
+// the index name as a string or the index specification as a document. This
+// option is only valid for MongoDB versions >= 4.2. Server versions < 4.2 will
+// return an error if this option is specified. The driver will return an error
+// if this option is specified during an unacknowledged write operation. The
+// driver will return an error if the hint parameter is a multi-key map. The
+// default value is nil, which means that no hint will be sent.
+func (uom *UpdateOneModel) SetHint(hint any) *UpdateOneModel {
+ uom.Hint = hint
+ return uom
+}
+
+// SetFilter specifies a filter to use to select the document to update. The filter must be a document containing query
+// operators. It cannot be nil. If the filter matches multiple documents, one will be selected from the matching
+// documents.
+func (uom *UpdateOneModel) SetFilter(filter any) *UpdateOneModel {
+ uom.Filter = filter
+ return uom
+}
+
+// SetUpdate specifies the modifications to be made to the selected document. The value must be a document containing
+// update operators (https://www.mongodb.com/docs/manual/reference/operator/update/). It cannot be nil or empty.
+func (uom *UpdateOneModel) SetUpdate(update any) *UpdateOneModel {
+ uom.Update = update
+ return uom
+}
+
+// SetArrayFilters specifies a set of filters to determine which elements should be modified when updating an array
+// field.
+func (uom *UpdateOneModel) SetArrayFilters(filters []any) *UpdateOneModel {
+ uom.ArrayFilters = filters
+ return uom
+}
+
+// SetCollation specifies a collation to use for string comparisons. The default is nil, meaning no collation will be
+// used.
+func (uom *UpdateOneModel) SetCollation(collation *options.Collation) *UpdateOneModel {
+ uom.Collation = collation
+ return uom
+}
+
+// SetUpsert specifies whether or not a new document should be inserted if no document matching the filter is found. If
+// an upsert is performed, the _id of the upserted document can be retrieved from the UpsertedIDs field of the
+// BulkWriteResult.
+func (uom *UpdateOneModel) SetUpsert(upsert bool) *UpdateOneModel {
+ uom.Upsert = &upsert
+ return uom
+}
+
+// SetSort specifies which document the operation updates if the query matches multiple documents. The first document
+// matched by the sort order will be updated. This option is only valid for MongoDB versions >= 8.0. The sort parameter
+// is evaluated sequentially, so the driver will return an error if it is a multi-key map (which is unordeded). The
+// default value is nil.
+func (uom *UpdateOneModel) SetSort(sort any) *UpdateOneModel {
+ uom.Sort = sort
+ return uom
+}
+
+func (*UpdateOneModel) writeModel() {}
+
+// UpdateManyModel is used to update multiple documents in a BulkWrite operation.
+//
+// See corresponding setter methods for documentation.
+type UpdateManyModel struct {
+ Collation *options.Collation
+ Upsert *bool
+ Filter any
+ Update any
+ ArrayFilters []any
+ Hint any
+}
+
+// NewUpdateManyModel creates a new UpdateManyModel.
+func NewUpdateManyModel() *UpdateManyModel {
+ return &UpdateManyModel{}
+}
+
+// SetHint specifies the index to use for the operation. This should either be
+// the index name as a string or the index specification as a document. This
+// option is only valid for MongoDB versions >= 4.2. Server versions < 4.2 will
+// return an error if this option is specified. The driver will return an error
+// if this option is specified during an unacknowledged write operation. The
+// driver will return an error if the hint parameter is a multi-key map. The
+// default value is nil, which means that no hint will be sent.
+func (umm *UpdateManyModel) SetHint(hint any) *UpdateManyModel {
+ umm.Hint = hint
+ return umm
+}
+
+// SetFilter specifies a filter to use to select documents to update. The filter must be a document containing query
+// operators. It cannot be nil.
+func (umm *UpdateManyModel) SetFilter(filter any) *UpdateManyModel {
+ umm.Filter = filter
+ return umm
+}
+
+// SetUpdate specifies the modifications to be made to the selected documents. The value must be a document containing
+// update operators (https://www.mongodb.com/docs/manual/reference/operator/update/). It cannot be nil or empty.
+func (umm *UpdateManyModel) SetUpdate(update any) *UpdateManyModel {
+ umm.Update = update
+ return umm
+}
+
+// SetArrayFilters specifies a set of filters to determine which elements should be modified when updating an array
+// field.
+func (umm *UpdateManyModel) SetArrayFilters(filters []any) *UpdateManyModel {
+ umm.ArrayFilters = filters
+ return umm
+}
+
+// SetCollation specifies a collation to use for string comparisons. The default is nil, meaning no collation will be
+// used.
+func (umm *UpdateManyModel) SetCollation(collation *options.Collation) *UpdateManyModel {
+ umm.Collation = collation
+ return umm
+}
+
+// SetUpsert specifies whether or not a new document should be inserted if no document matching the filter is found. If
+// an upsert is performed, the _id of the upserted document can be retrieved from the UpsertedIDs field of the
+// BulkWriteResult.
+func (umm *UpdateManyModel) SetUpsert(upsert bool) *UpdateManyModel {
+ umm.Upsert = &upsert
+ return umm
+}
+
+func (*UpdateManyModel) writeModel() {}
diff --git a/vendor/go.mongodb.org/mongo-driver/v2/mongo/change_stream.go b/vendor/go.mongodb.org/mongo-driver/v2/mongo/change_stream.go
new file mode 100644
index 0000000..be8b71c
--- /dev/null
+++ b/vendor/go.mongodb.org/mongo-driver/v2/mongo/change_stream.go
@@ -0,0 +1,798 @@
+// Copyright (C) MongoDB, Inc. 2017-present.
+//
+// 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
+
+package mongo
+
+import (
+ "context"
+ "errors"
+ "fmt"
+ "reflect"
+ "strconv"
+
+ "go.mongodb.org/mongo-driver/v2/bson"
+ "go.mongodb.org/mongo-driver/v2/internal/csot"
+ "go.mongodb.org/mongo-driver/v2/internal/driverutil"
+ "go.mongodb.org/mongo-driver/v2/internal/mongoutil"
+ "go.mongodb.org/mongo-driver/v2/internal/serverselector"
+ "go.mongodb.org/mongo-driver/v2/mongo/options"
+ "go.mongodb.org/mongo-driver/v2/mongo/readconcern"
+ "go.mongodb.org/mongo-driver/v2/mongo/readpref"
+ "go.mongodb.org/mongo-driver/v2/x/bsonx/bsoncore"
+ "go.mongodb.org/mongo-driver/v2/x/mongo/driver"
+ "go.mongodb.org/mongo-driver/v2/x/mongo/driver/description"
+ "go.mongodb.org/mongo-driver/v2/x/mongo/driver/operation"
+ "go.mongodb.org/mongo-driver/v2/x/mongo/driver/session"
+)
+
+var (
+ // ErrMissingResumeToken indicates that a change stream notification from the server did not contain a resume token.
+ ErrMissingResumeToken = errors.New("cannot provide resume functionality when the resume token is missing")
+ // ErrNilCursor indicates that the underlying cursor for the change stream is nil.
+ ErrNilCursor = errors.New("cursor is nil")
+
+ minResumableLabelWireVersion int32 = 9 // Wire version at which the server includes the resumable error label
+ networkErrorLabel = "NetworkError"
+ resumableErrorLabel = "ResumableChangeStreamError"
+ errorCursorNotFound int32 = 43 // CursorNotFound error code
+
+ // Allowlist of error codes that are considered resumable.
+ resumableChangeStreamErrors = map[int32]struct{}{
+ 6: {}, // HostUnreachable
+ 7: {}, // HostNotFound
+ 89: {}, // NetworkTimeout
+ 91: {}, // ShutdownInProgress
+ 189: {}, // PrimarySteppedDown
+ 262: {}, // ExceededTimeLimit
+ 9001: {}, // SocketException
+ 10107: {}, // NotPrimary
+ 11600: {}, // InterruptedAtShutdown
+ 11602: {}, // InterruptedDueToReplStateChange
+ 13435: {}, // NotPrimaryNoSecondaryOK
+ 13436: {}, // NotPrimaryOrSecondary
+ 63: {}, // StaleShardVersion
+ 150: {}, // StaleEpoch
+ 13388: {}, // StaleConfig
+ 234: {}, // RetryChangeStream
+ 133: {}, // FailedToSatisfyReadPreference
+ }
+)
+
+// ChangeStream is used to iterate over a stream of events. Each event can be decoded into a Go type via the Decode
+// method or accessed as raw BSON via the Current field. This type is not goroutine safe and must not be used
+// concurrently by multiple goroutines. For more information about change streams, see
+// https://www.mongodb.com/docs/manual/changeStreams/.
+type ChangeStream struct {
+ // Current is the BSON bytes of the current event. This property is only valid until the next call to Next or
+ // TryNext. If continued access is required, a copy must be made.
+ Current bson.Raw
+
+ aggregate *operation.Aggregate
+ pipelineSlice []bsoncore.Document
+ pipelineOptions map[string]bsoncore.Value
+ cursor changeStreamCursor
+ cursorOptions driver.CursorOptions
+ batch []bsoncore.Document
+ resumeToken bson.Raw
+ err error
+ sess *session.Client
+ client *Client
+ bsonOpts *options.BSONOptions
+ registry *bson.Registry
+ streamType StreamType
+ options *options.ChangeStreamOptions
+ selector description.ServerSelector
+ operationTime *bson.Timestamp
+ wireVersion *description.VersionRange
+}
+
+type changeStreamConfig struct {
+ readConcern *readconcern.ReadConcern
+ readPreference *readpref.ReadPref
+ client *Client
+ bsonOpts *options.BSONOptions
+ registry *bson.Registry
+ streamType StreamType
+ collectionName string
+ databaseName string
+ crypt driver.Crypt
+}
+
+func newChangeStream(ctx context.Context, config changeStreamConfig, pipeline any,
+ opts ...options.Lister[options.ChangeStreamOptions],
+) (*ChangeStream, error) {
+ if ctx == nil {
+ ctx = context.Background()
+ }
+
+ cursorOpts := config.client.createBaseCursorOptions(config.client.retryReads)
+
+ cursorOpts.MarshalValueEncoderFn = newEncoderFn(config.bsonOpts, config.registry)
+
+ args, err := mongoutil.NewOptions[options.ChangeStreamOptions](opts...)
+ if err != nil {
+ return nil, err
+ }
+
+ cs := &ChangeStream{
+ client: config.client,
+ bsonOpts: config.bsonOpts,
+ registry: config.registry,
+ streamType: config.streamType,
+ options: args,
+ selector: &serverselector.Composite{
+ Selectors: []description.ServerSelector{
+ &serverselector.ReadPref{ReadPref: config.readPreference},
+ &serverselector.Latency{Latency: config.client.localThreshold},
+ },
+ },
+ cursorOptions: cursorOpts,
+ }
+
+ cs.sess = sessionFromContext(ctx)
+ if cs.sess == nil && cs.client.sessionPool != nil {
+ cs.sess = session.NewImplicitClientSession(cs.client.sessionPool, cs.client.id)
+ }
+ if cs.err = cs.client.validSession(cs.sess); cs.err != nil {
+ closeImplicitSession(cs.sess)
+ return nil, cs.Err()
+ }
+
+ cs.aggregate = operation.NewAggregate(nil).
+ ReadPreference(config.readPreference).ReadConcern(config.readConcern).
+ Deployment(cs.client.deployment).ClusterClock(cs.client.clock).
+ CommandMonitor(cs.client.monitor).Session(cs.sess).ServerSelector(cs.selector).
+ Retry(driver.RetryNone).MaxAdaptiveRetries(cursorOpts.MaxAdaptiveRetries).
+ EnableOverloadRetargeting(cursorOpts.EnableOverloadRetargeting).
+ ServerAPI(cs.client.serverAPI).Crypt(config.crypt).Timeout(cs.client.timeout).
+ Authenticator(cs.client.authenticator)
+
+ if cs.options.Collation != nil {
+ cs.aggregate.Collation(bsoncore.Document(toDocument(cs.options.Collation)))
+ }
+ if cs.options.Comment != nil {
+ comment, err := marshalValue(cs.options.Comment, cs.bsonOpts, cs.registry)
+ if err != nil {
+ return nil, err
+ }
+
+ cs.aggregate.Comment(comment)
+ cs.cursorOptions.Comment = comment
+ }
+ if cs.options.BatchSize != nil {
+ cs.aggregate.BatchSize(*cs.options.BatchSize)
+ cs.cursorOptions.BatchSize = *cs.options.BatchSize
+ }
+ if cs.options.MaxAwaitTime != nil {
+ cs.cursorOptions.SetMaxAwaitTime(*cs.options.MaxAwaitTime)
+ }
+ if cs.options.Custom != nil {
+ // Marshal all custom options before passing to the initial aggregate. Return
+ // any errors from Marshaling.
+ customOptions := make(map[string]bsoncore.Value)
+ for optionName, optionValue := range cs.options.Custom {
+ optionValueBSON, err := marshalValue(optionValue, nil, cs.registry)
+ if err != nil {
+ cs.err = err
+ closeImplicitSession(cs.sess)
+ return nil, cs.Err()
+ }
+ customOptions[optionName] = optionValueBSON
+ }
+ cs.aggregate.CustomOptions(customOptions)
+ }
+ if cs.options.CustomPipeline != nil {
+ // Marshal all custom pipeline options before building pipeline slice. Return
+ // any errors from Marshaling.
+ cs.pipelineOptions = make(map[string]bsoncore.Value)
+ for optionName, optionValue := range cs.options.CustomPipeline {
+ optionValueBSON, err := marshalValue(optionValue, nil, cs.registry)
+ if err != nil {
+ cs.err = err
+ closeImplicitSession(cs.sess)
+ return nil, cs.Err()
+ }
+ cs.pipelineOptions[optionName] = optionValueBSON
+ }
+ }
+
+ switch cs.streamType {
+ case ClientStream:
+ cs.aggregate.Database("admin")
+ case DatabaseStream:
+ cs.aggregate.Database(config.databaseName)
+ case CollectionStream:
+ cs.aggregate.Collection(config.collectionName).Database(config.databaseName)
+ default:
+ closeImplicitSession(cs.sess)
+ return nil, fmt.Errorf("must supply a valid StreamType in config, instead of %v", cs.streamType)
+ }
+
+ // When starting a change stream, cache startAfter as the first resume token if it is set. If not, cache
+ // resumeAfter. If neither is set, do not cache a resume token.
+ resumeToken := cs.options.StartAfter
+ if resumeToken == nil {
+ resumeToken = cs.options.ResumeAfter
+ }
+ var marshaledToken bson.Raw
+ if resumeToken != nil {
+ if marshaledToken, cs.err = bson.Marshal(resumeToken); cs.err != nil {
+ closeImplicitSession(cs.sess)
+ return nil, cs.Err()
+ }
+ }
+ cs.resumeToken = marshaledToken
+
+ if cs.err = cs.buildPipelineSlice(pipeline); cs.err != nil {
+ closeImplicitSession(cs.sess)
+ return nil, cs.Err()
+ }
+ var pipelineArr bsoncore.Document
+ pipelineArr, cs.err = cs.pipelineToBSON()
+ cs.aggregate.Pipeline(pipelineArr)
+
+ if cs.err = cs.executeOperation(ctx, false); cs.err != nil {
+ closeImplicitSession(cs.sess)
+ return nil, cs.Err()
+ }
+
+ return cs, cs.Err()
+}
+
+func (cs *ChangeStream) createOperationDeployment(ctx context.Context) (*changeStreamDeployment, error) {
+ var cancel context.CancelFunc
+
+ deployment := &changeStreamDeployment{
+ topologyKind: cs.client.deployment.Kind(),
+ }
+ deployment.close = func() error {
+ var err error
+ if deployment.conn != nil {
+ err = deployment.conn.Close()
+ }
+ if cancel != nil {
+ cancel()
+ }
+ return err
+ }
+ deployment.reset = func() error {
+ _ = deployment.close()
+
+ var err error
+ var connCtx context.Context
+ connCtx, cancel = csot.WithServerSelectionTimeout(ctx, cs.client.deployment.GetServerSelectionTimeout())
+ deployment.server, err = cs.client.deployment.SelectServer(connCtx, cs.selector)
+ if err != nil {
+ cancel()
+ return err
+ }
+ deployment.conn, err = deployment.server.Connection(connCtx)
+ if err != nil {
+ cancel()
+ return err
+ }
+ cs.wireVersion = deployment.conn.Description().WireVersion
+ return nil
+ }
+ if err := deployment.reset(); err != nil {
+ return nil, err
+ }
+ return deployment, nil
+}
+
+func (cs *ChangeStream) executeOperation(ctx context.Context, resuming bool) error {
+ var deployment *changeStreamDeployment
+
+ // Apply the client-level timeout if the operation-level timeout is not set.
+ ctx, cancel := csot.WithTimeout(ctx, cs.client.timeout)
+ defer cancel()
+
+ deployment, cs.err = cs.createOperationDeployment(ctx)
+ if cs.err != nil {
+ return cs.Err()
+ }
+ defer func() {
+ _ = deployment.close()
+ }()
+
+ cs.aggregate.Deployment(deployment)
+
+ if resuming {
+ cs.replaceOptions(cs.wireVersion)
+
+ csOptDoc, err := cs.createPipelineOptionsDoc()
+ if err != nil {
+ return err
+ }
+ pipIdx, pipDoc := bsoncore.AppendDocumentStart(nil)
+ pipDoc = bsoncore.AppendDocumentElement(pipDoc, "$changeStream", csOptDoc)
+ if pipDoc, cs.err = bsoncore.AppendDocumentEnd(pipDoc, pipIdx); cs.err != nil {
+ return cs.Err()
+ }
+ cs.pipelineSlice[0] = pipDoc
+
+ var plArr bsoncore.Document
+ if plArr, cs.err = cs.pipelineToBSON(); cs.err != nil {
+ return cs.Err()
+ }
+ cs.aggregate.Pipeline(plArr)
+ }
+
+ // Execute the aggregate, retrying on retryable errors once (1) if retryable reads are enabled and
+ // infinitely (-1) if context is a Timeout context.
+ var retries int
+ if cs.client.retryReads {
+ retries = 1
+ }
+ if csot.IsTimeoutContext(ctx) {
+ retries = -1
+ }
+
+ var err error
+AggregateExecuteLoop:
+ for {
+ err = cs.aggregate.Execute(ctx)
+ // If no error or no retries remain, do not retry.
+ if err == nil || retries == 0 {
+ break AggregateExecuteLoop
+ }
+
+ var tt driver.Error
+ if errors.As(err, &tt) {
+ // If error is not retryable, do not retry.
+ if !tt.RetryableRead() {
+ break AggregateExecuteLoop
+ }
+
+ // If error is retryable: subtract 1 from retries, redo server selection, checkout
+ // a connection, and restart loop.
+ retries--
+
+ // Reset deployment.
+ err = deployment.reset()
+ if err != nil {
+ break AggregateExecuteLoop
+ }
+ } else {
+ // Do not retry if error is not a driver error.
+ break AggregateExecuteLoop
+ }
+ }
+ if err != nil {
+ cs.err = wrapErrors(err)
+ return cs.err
+ }
+
+ cr := cs.aggregate.ResultCursorResponse()
+ cr.Server = deployment.server
+
+ cs.cursor, cs.err = driver.NewBatchCursor(cr, cs.sess, cs.client.clock, cs.cursorOptions)
+ if cs.err = wrapErrors(cs.err); cs.err != nil {
+ return cs.Err()
+ }
+
+ cs.updatePbrtFromCommand()
+ if cs.options.StartAtOperationTime == nil && cs.options.ResumeAfter == nil &&
+ cs.options.StartAfter == nil && cs.wireVersion.Max >= 7 &&
+ cs.emptyBatch() && cs.resumeToken == nil {
+ cs.operationTime = cs.sess.OperationTime
+ }
+
+ return cs.Err()
+}
+
+// Updates the post batch resume token after a successful aggregate or getMore operation.
+func (cs *ChangeStream) updatePbrtFromCommand() {
+ // Only cache the pbrt if an empty batch was returned and a pbrt was included
+ if pbrt := cs.cursor.PostBatchResumeToken(); cs.emptyBatch() && pbrt != nil {
+ cs.resumeToken = bson.Raw(pbrt)
+ }
+}
+
+func (cs *ChangeStream) storeResumeToken() error {
+ // If cs.Current is the last document in the batch and a pbrt is included, cache the pbrt
+ // Otherwise, cache the _id of the document
+ var tokenDoc bson.Raw
+ if len(cs.batch) == 0 {
+ if pbrt := cs.cursor.PostBatchResumeToken(); pbrt != nil {
+ tokenDoc = bson.Raw(pbrt)
+ }
+ }
+
+ if tokenDoc == nil {
+ var ok bool
+ tokenDoc, ok = cs.Current.Lookup("_id").DocumentOK()
+ if !ok {
+ _ = cs.Close(context.Background())
+ return ErrMissingResumeToken
+ }
+ }
+
+ cs.resumeToken = tokenDoc
+ return nil
+}
+
+func (cs *ChangeStream) buildPipelineSlice(pipeline any) error {
+ val := reflect.ValueOf(pipeline)
+ if !val.IsValid() || (val.Kind() != reflect.Slice) {
+ cs.err = errors.New("can only marshal slices and arrays into aggregation pipelines, but got invalid")
+ return cs.err
+ }
+
+ cs.pipelineSlice = make([]bsoncore.Document, 0, val.Len()+1)
+
+ csIdx, csDoc := bsoncore.AppendDocumentStart(nil)
+
+ csDocTemp, err := cs.createPipelineOptionsDoc()
+ if err != nil {
+ return err
+ }
+ csDoc = bsoncore.AppendDocumentElement(csDoc, "$changeStream", csDocTemp)
+ csDoc, cs.err = bsoncore.AppendDocumentEnd(csDoc, csIdx)
+ if cs.err != nil {
+ return cs.err
+ }
+ cs.pipelineSlice = append(cs.pipelineSlice, csDoc)
+
+ for i := 0; i < val.Len(); i++ {
+ var elem []byte
+ elem, cs.err = marshal(val.Index(i).Interface(), cs.bsonOpts, cs.registry)
+ if cs.err != nil {
+ return cs.err
+ }
+
+ cs.pipelineSlice = append(cs.pipelineSlice, elem)
+ }
+
+ return cs.err
+}
+
+func (cs *ChangeStream) createPipelineOptionsDoc() (bsoncore.Document, error) {
+ plDocIdx, plDoc := bsoncore.AppendDocumentStart(nil)
+
+ if cs.streamType == ClientStream {
+ plDoc = bsoncore.AppendBooleanElement(plDoc, "allChangesForCluster", true)
+ }
+
+ if cs.options.FullDocument != nil && *cs.options.FullDocument != options.Default {
+ plDoc = bsoncore.AppendStringElement(plDoc, "fullDocument", string(*cs.options.FullDocument))
+ }
+
+ if cs.options.FullDocumentBeforeChange != nil {
+ plDoc = bsoncore.AppendStringElement(plDoc, "fullDocumentBeforeChange", string(*cs.options.FullDocumentBeforeChange))
+ }
+
+ if cs.options.ResumeAfter != nil {
+ var raDoc bsoncore.Document
+ raDoc, cs.err = marshal(cs.options.ResumeAfter, cs.bsonOpts, cs.registry)
+ if cs.err != nil {
+ return nil, cs.err
+ }
+
+ plDoc = bsoncore.AppendDocumentElement(plDoc, "resumeAfter", raDoc)
+ }
+
+ if cs.options.ShowExpandedEvents != nil {
+ plDoc = bsoncore.AppendBooleanElement(plDoc, "showExpandedEvents", *cs.options.ShowExpandedEvents)
+ }
+
+ if cs.options.StartAfter != nil {
+ var saDoc bsoncore.Document
+ saDoc, cs.err = marshal(cs.options.StartAfter, cs.bsonOpts, cs.registry)
+ if cs.err != nil {
+ return nil, cs.err
+ }
+
+ plDoc = bsoncore.AppendDocumentElement(plDoc, "startAfter", saDoc)
+ }
+
+ if cs.options.StartAtOperationTime != nil {
+ plDoc = bsoncore.AppendTimestampElement(plDoc, "startAtOperationTime", cs.options.StartAtOperationTime.T, cs.options.StartAtOperationTime.I)
+ }
+
+ // Append custom pipeline options.
+ for optionName, optionValue := range cs.pipelineOptions {
+ plDoc = bsoncore.AppendValueElement(plDoc, optionName, optionValue)
+ }
+
+ if plDoc, cs.err = bsoncore.AppendDocumentEnd(plDoc, plDocIdx); cs.err != nil {
+ return nil, cs.err
+ }
+
+ return plDoc, nil
+}
+
+func (cs *ChangeStream) pipelineToBSON() (bsoncore.Document, error) {
+ pipelineDocIdx, pipelineArr := bsoncore.AppendArrayStart(nil)
+ for i, doc := range cs.pipelineSlice {
+ pipelineArr = bsoncore.AppendDocumentElement(pipelineArr, strconv.Itoa(i), doc)
+ }
+ if pipelineArr, cs.err = bsoncore.AppendArrayEnd(pipelineArr, pipelineDocIdx); cs.err != nil {
+ return nil, cs.err
+ }
+ return pipelineArr, cs.err
+}
+
+func (cs *ChangeStream) replaceOptions(wireVersion *description.VersionRange) {
+ // Cached resume token: use the resume token as the resumeAfter option and set no other resume options
+ if cs.resumeToken != nil {
+ cs.options.ResumeAfter = cs.resumeToken
+ cs.options.StartAfter = nil
+ cs.options.StartAtOperationTime = nil
+ return
+ }
+
+ // No cached resume token but cached operation time: use the operation time as the startAtOperationTime option and
+ // set no other resume options
+ if (cs.sess.OperationTime != nil || cs.options.StartAtOperationTime != nil) && wireVersion.Max >= 7 {
+ opTime := cs.options.StartAtOperationTime
+ if cs.operationTime != nil {
+ opTime = cs.sess.OperationTime
+ }
+
+ cs.options.StartAtOperationTime = opTime
+ cs.options.ResumeAfter = nil
+ cs.options.StartAfter = nil
+ return
+ }
+
+ // No cached resume token or operation time: set none of the resume options
+ cs.options.ResumeAfter = nil
+ cs.options.StartAfter = nil
+ cs.options.StartAtOperationTime = nil
+}
+
+// ID returns the ID for this change stream, or 0 if the cursor has been closed or exhausted.
+func (cs *ChangeStream) ID() int64 {
+ if cs.cursor == nil {
+ return 0
+ }
+ return cs.cursor.ID()
+}
+
+// RemainingBatchLength returns the number of documents left in the current batch. If this returns zero, the subsequent
+// call to Next or TryNext will do a network request to fetch the next batch.
+func (cs *ChangeStream) RemainingBatchLength() int {
+ return len(cs.batch)
+}
+
+// SetBatchSize sets the number of documents to fetch from the database with
+// each iteration of the ChangeStream's "Next" or "TryNext" method. This setting
+// only affects subsequent document batches fetched from the database.
+func (cs *ChangeStream) SetBatchSize(size int32) {
+ // Set batch size on the cursor options also so any "resumed" change stream
+ // cursors will pick up the latest batch size setting.
+ cs.cursorOptions.BatchSize = size
+ if cs.cursor == nil {
+ return
+ }
+ cs.cursor.SetBatchSize(size)
+}
+
+// Decode will unmarshal the current event document into val and return any errors from the unmarshalling process
+// without any modification. If val is nil or is a typed nil, an error will be returned.
+func (cs *ChangeStream) Decode(val any) error {
+ if cs.cursor == nil {
+ return ErrNilCursor
+ }
+
+ dec := getDecoder(cs.Current, cs.bsonOpts, cs.registry)
+ return dec.Decode(val)
+}
+
+// Err returns the last error seen by the change stream, or nil if no errors has occurred.
+func (cs *ChangeStream) Err() error {
+ if cs.err != nil {
+ return wrapErrors(cs.err)
+ }
+ if cs.cursor == nil {
+ return nil
+ }
+
+ return wrapErrors(cs.cursor.Err())
+}
+
+// Close closes this change stream and the underlying cursor. Next and TryNext must not be called after Close has been
+// called. Close is idempotent. After the first call, any subsequent calls will not change the state.
+func (cs *ChangeStream) Close(ctx context.Context) error {
+ if ctx == nil {
+ ctx = context.Background()
+ }
+
+ defer closeImplicitSession(cs.sess)
+
+ if cs.cursor == nil {
+ return nil // cursor is already closed
+ }
+
+ cs.err = wrapErrors(cs.cursor.Close(ctx))
+ cs.cursor = nil
+ return cs.Err()
+}
+
+// ResumeToken returns the last cached resume token for this change stream, or nil if a resume token has not been
+// stored.
+func (cs *ChangeStream) ResumeToken() bson.Raw {
+ return cs.resumeToken
+}
+
+// Next gets the next event for this change stream. It returns true if there
+// were no errors and the next event document is available.
+//
+// Next blocks until an event is available, an error occurs, or ctx expires.
+// If ctx expires, the error will be set to ctx.Err(). In an error case, Next
+// will return false.
+//
+// If Next returns false, subsequent calls will also return false.
+func (cs *ChangeStream) Next(ctx context.Context) bool {
+ return cs.next(ctx, false)
+}
+
+// TryNext attempts to get the next event for this change stream. It returns
+// true if there were no errors and the next event document is available.
+//
+// TryNext returns false if the change stream is closed by the server, an error
+// occurs when getting changes from the server, the next change is not yet
+// available, or ctx expires.
+//
+// If ctx expires, the error will be set to ctx.Err(). Users can either call
+// TryNext again or close the existing change stream and create a new one. It is
+// suggested to close and re-create the stream with ah higher timeout if the
+// timeout occurs before any events have been received, which is a signal that
+// the server is timing out before it can finish processing the existing oplog.
+//
+// If TryNext returns false and an error occurred or the change stream was
+// closed (i.e. cs.Err() != nil || cs.ID() == 0), subsequent attempts will also
+// return false. Otherwise, it is safe to call TryNext again until a change is
+// available.
+//
+// This method requires driver version >= 1.2.0.
+func (cs *ChangeStream) TryNext(ctx context.Context) bool {
+ return cs.next(ctx, true)
+}
+
+func (cs *ChangeStream) next(ctx context.Context, nonBlocking bool) bool {
+ // return false right away if the change stream has already errored or if cursor is closed.
+ if cs.err != nil {
+ return false
+ }
+
+ if ctx == nil {
+ ctx = context.Background()
+ }
+
+ if len(cs.batch) == 0 {
+ cs.loopNext(ctx, nonBlocking)
+ if cs.err != nil {
+ cs.err = wrapErrors(cs.err)
+ return false
+ }
+ if len(cs.batch) == 0 {
+ return false
+ }
+ }
+
+ // successfully got non-empty batch
+ cs.Current = bson.Raw(cs.batch[0])
+ cs.batch = cs.batch[1:]
+ if cs.err = cs.storeResumeToken(); cs.err != nil {
+ return false
+ }
+ return true
+}
+
+func (cs *ChangeStream) loopNext(ctx context.Context, nonBlocking bool) {
+ // To avoid unnecessary socket timeouts, we attempt to short-circuit tailable
+ // awaitData "getMore" operations by ensuring that the maxAwaitTimeMS is less
+ // than the operation timeout.
+ //
+ // The specifications assume that drivers iteratively apply the timeout
+ // provided at the constructor level (e.g., (*collection).Find) for tailable
+ // awaitData cursors:
+ //
+ // If set, drivers MUST apply the timeoutMS option to the initial aggregate
+ // operation. Drivers MUST also apply the original timeoutMS value to each
+ // next call on the change stream but MUST NOT use it to derive a maxTimeMS
+ // field for getMore commands.
+ //
+ // The Go Driver might decide to support the above behavior with DRIVERS-2722.
+ // The principal concern is that it would be unexpected for users to apply an
+ // operation-level timeout via contexts to a constructor and then that timeout
+ // later be applied while working with a resulting cursor. Instead, it is more
+ // idiomatic to apply the timeout to the context passed to Next or TryNext.
+ if cs.options != nil && !nonBlocking {
+ maxAwaitTime := cs.cursorOptions.MaxAwaitTime
+
+ // If maxAwaitTime is not set, this check is unnecessary.
+ if maxAwaitTime != nil && !mongoutil.TimeoutWithinContext(ctx, *maxAwaitTime) {
+ cs.err = fmt.Errorf("MaxAwaitTime must be less than the operation timeout")
+
+ return
+ }
+ }
+
+ // Apply the client-level timeout if the operation-level timeout is not set.
+ // This calculation is also done in "executeOperation" but cursor.Next is also
+ // blocking and should honor client-level timeouts.
+ ctx, cancel := csot.WithTimeout(ctx, cs.client.timeout)
+ defer cancel()
+
+ for {
+ if cs.cursor == nil {
+ return
+ }
+
+ if cs.cursor.Next(ctx) {
+ // non-empty batch returned
+ cs.batch, cs.err = cs.cursor.Batch().Documents()
+ return
+ }
+
+ cs.err = wrapErrors(cs.cursor.Err())
+ if cs.err == nil {
+ // Check if cursor is alive
+ if cs.ID() == 0 {
+ return
+ }
+
+ // If a getMore was done but the batch was empty, the batch cursor will return false with no error.
+ // Update the tracked resume token to catch the post batch resume token from the server response.
+ cs.updatePbrtFromCommand()
+ if nonBlocking {
+ // stop after a successful getMore, even though the batch was empty
+ return
+ }
+ continue // loop getMore until a non-empty batch is returned or an error occurs
+ }
+
+ if !cs.isResumableError() {
+ return
+ }
+
+ // ignore error from cursor close because if the cursor is deleted or errors we tried to close it and will remake and try to get next batch
+ _ = cs.cursor.Close(ctx)
+ if cs.err = cs.executeOperation(ctx, true); cs.err != nil {
+ return
+ }
+ }
+}
+
+func (cs *ChangeStream) isResumableError() bool {
+ var commandErr CommandError
+ if !errors.As(cs.err, &commandErr) || commandErr.HasErrorLabel(networkErrorLabel) {
+ // All non-server errors or network errors are resumable.
+ return true
+ }
+
+ if commandErr.Code == errorCursorNotFound {
+ return true
+ }
+
+ // For wire versions 9 and above, a server error is resumable if it has the ResumableChangeStreamError label.
+ if cs.wireVersion != nil && driverutil.VersionRangeIncludes(*cs.wireVersion, minResumableLabelWireVersion) {
+ return commandErr.HasErrorLabel(resumableErrorLabel)
+ }
+
+ // For wire versions below 9, a server error is resumable if its code is on the allowlist.
+ _, resumable := resumableChangeStreamErrors[commandErr.Code]
+ return resumable
+}
+
+// Returns true if the underlying cursor's batch is empty
+func (cs *ChangeStream) emptyBatch() bool {
+ return cs.cursor.Batch().Empty()
+}
+
+// StreamType represents the cluster type against which a ChangeStream was created.
+type StreamType uint8
+
+// These constants represent valid change stream types. A change stream can be initialized over a collection, all
+// collections in a database, or over a cluster.
+const (
+ CollectionStream StreamType = iota
+ DatabaseStream
+ ClientStream
+)
diff --git a/vendor/go.mongodb.org/mongo-driver/v2/mongo/change_stream_deployment.go b/vendor/go.mongodb.org/mongo-driver/v2/mongo/change_stream_deployment.go
new file mode 100644
index 0000000..b5a88e3
--- /dev/null
+++ b/vendor/go.mongodb.org/mongo-driver/v2/mongo/change_stream_deployment.go
@@ -0,0 +1,80 @@
+// Copyright (C) MongoDB, Inc. 2017-present.
+//
+// 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
+
+package mongo
+
+import (
+ "context"
+ "time"
+
+ "go.mongodb.org/mongo-driver/v2/x/mongo/driver"
+ "go.mongodb.org/mongo-driver/v2/x/mongo/driver/description"
+ "go.mongodb.org/mongo-driver/v2/x/mongo/driver/mnet"
+)
+
+type changeStreamDeployment struct {
+ topologyKind description.TopologyKind
+ server driver.Server
+ conn *mnet.Connection
+
+ reset func() error
+ close func() error
+}
+
+var (
+ _ driver.Deployment = (*changeStreamDeployment)(nil)
+ _ driver.Server = (*changeStreamDeployment)(nil)
+ _ driver.ErrorProcessor = (*changeStreamDeployment)(nil)
+)
+
+func (c *changeStreamDeployment) SelectServer(context.Context, description.ServerSelector) (driver.Server, error) {
+ return c, nil
+}
+
+func (c *changeStreamDeployment) Kind() description.TopologyKind {
+ return c.topologyKind
+}
+
+func (c *changeStreamDeployment) Connection(context.Context) (*mnet.Connection, error) {
+ var err error
+ // TODO(GODRIVER-3905): replace c.conn.Closed() with c.conn.IsAlive()
+ // once the bufio.Reader infrastructure from GODRIVER-3603 lands.
+ // The post-3603 form will be:
+ //
+ // if c.conn == nil || !c.conn.IsAlive() {
+ // err = c.reset()
+ // }
+ //
+ // IsAlive performs a non-destructive Peek under a short read deadline,
+ // catching the silent-death cases this Closed() check currently misses
+ // (server-side close, peer RST, network drop already noticed by the
+ // kernel, TLS abort). The narrow "did we call Close?" semantics of the
+ // current flag aren't useful here — we want to know whether the cached
+ // connection is reusable, not who terminated it.
+ if c.conn == nil || c.conn.Closed() {
+ err = c.reset()
+ }
+ return c.conn, err
+}
+
+func (c *changeStreamDeployment) RTTMonitor() driver.RTTMonitor {
+ return c.server.RTTMonitor()
+}
+
+func (c *changeStreamDeployment) ProcessError(err error, describer mnet.Describer) driver.ProcessErrorResult {
+ ep, ok := c.server.(driver.ErrorProcessor)
+ if !ok {
+ return driver.NoChange
+ }
+
+ return ep.ProcessError(err, describer)
+}
+
+// GetServerSelectionTimeout returns zero as a server selection timeout is not
+// applicable for change stream deployments.
+func (*changeStreamDeployment) GetServerSelectionTimeout() time.Duration {
+ return 0
+}
diff --git a/vendor/go.mongodb.org/mongo-driver/v2/mongo/client.go b/vendor/go.mongodb.org/mongo-driver/v2/mongo/client.go
new file mode 100644
index 0000000..14b65b6
--- /dev/null
+++ b/vendor/go.mongodb.org/mongo-driver/v2/mongo/client.go
@@ -0,0 +1,1082 @@
+// Copyright (C) MongoDB, Inc. 2017-present.
+//
+// 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
+
+package mongo
+
+import (
+ "context"
+ "errors"
+ "fmt"
+ "net/http"
+ "sync"
+ "sync/atomic"
+ "time"
+
+ "go.mongodb.org/mongo-driver/v2/bson"
+ "go.mongodb.org/mongo-driver/v2/event"
+ "go.mongodb.org/mongo-driver/v2/internal/httputil"
+ "go.mongodb.org/mongo-driver/v2/internal/logger"
+ "go.mongodb.org/mongo-driver/v2/internal/mongoutil"
+ "go.mongodb.org/mongo-driver/v2/internal/optionsutil"
+ "go.mongodb.org/mongo-driver/v2/internal/ptrutil"
+ "go.mongodb.org/mongo-driver/v2/internal/serverselector"
+ "go.mongodb.org/mongo-driver/v2/internal/uuid"
+ "go.mongodb.org/mongo-driver/v2/mongo/options"
+ "go.mongodb.org/mongo-driver/v2/mongo/readconcern"
+ "go.mongodb.org/mongo-driver/v2/mongo/readpref"
+ "go.mongodb.org/mongo-driver/v2/mongo/writeconcern"
+ "go.mongodb.org/mongo-driver/v2/x/bsonx/bsoncore"
+ "go.mongodb.org/mongo-driver/v2/x/mongo/driver"
+ "go.mongodb.org/mongo-driver/v2/x/mongo/driver/auth"
+ "go.mongodb.org/mongo-driver/v2/x/mongo/driver/description"
+ "go.mongodb.org/mongo-driver/v2/x/mongo/driver/mongocrypt"
+ mcopts "go.mongodb.org/mongo-driver/v2/x/mongo/driver/mongocrypt/options"
+ "go.mongodb.org/mongo-driver/v2/x/mongo/driver/operation"
+ "go.mongodb.org/mongo-driver/v2/x/mongo/driver/session"
+ "go.mongodb.org/mongo-driver/v2/x/mongo/driver/topology"
+)
+
+const (
+ defaultLocalThreshold = 15 * time.Millisecond
+ defaultMaxPoolSize = 100
+ defaultAdaptiveRetries uint = 2
+)
+
+var (
+ // keyVaultCollOpts specifies options used to communicate with the key vault collection
+ keyVaultCollOpts = options.Collection().SetReadConcern(readconcern.Majority()).
+ SetWriteConcern(writeconcern.Majority())
+
+ endSessionsBatchSize = 10000
+)
+
+// Client is a handle representing a pool of connections to a MongoDB deployment. It is safe for concurrent use by
+// multiple goroutines.
+//
+// The Client type opens and closes connections automatically and maintains a pool of idle connections. For
+// connection pool configuration options, see documentation for the ClientOptions type in the mongo/options package.
+type Client struct {
+ id uuid.UUID
+ deployment driver.Deployment
+ localThreshold time.Duration
+ retryWrites bool
+ retryReads bool
+ maxAdaptiveRetries *uint
+ enableOverloadRetargeting bool
+ clock *session.ClusterClock
+ readPreference *readpref.ReadPref
+ readConcern *readconcern.ReadConcern
+ writeConcern *writeconcern.WriteConcern
+ bsonOpts *options.BSONOptions
+ registry *bson.Registry
+ monitor *event.CommandMonitor
+ serverAPI *driver.ServerAPIOptions
+ serverMonitor *event.ServerMonitor
+ sessionPool *session.Pool
+ timeout *time.Duration
+ httpClient *http.Client
+ logger *logger.Logger
+ currentDriverInfo *atomic.Pointer[options.DriverInfo]
+ seenDriverInfo sync.Map
+
+ // in-use encryption fields
+ isAutoEncryptionSet bool
+ keyVaultClientFLE *Client
+ keyVaultCollFLE *Collection
+ mongocryptdFLE *mongocryptdClient
+ cryptFLE driver.Crypt
+ metadataClientFLE *Client
+ internalClientFLE *Client
+ encryptedFieldsMap map[string]any
+ authenticator driver.Authenticator
+}
+
+// Connect creates a new Client with the given configuration options.
+//
+// Connect returns an error if the configuration options are invalid, but does
+// not validate that the MongoDB deployment is reachable. To verify that the
+// deployment is reachable, call [Client.Ping].
+//
+// When creating an [options.ClientOptions], the order the methods are called
+// matters. Later option setter calls overwrite the values from previous option
+// setter calls, including the ApplyURI method. This allows callers to
+// determine the order of precedence for setting options. For instance, if
+// ApplyURI is called before SetAuth, the Credential from SetAuth will
+// overwrite the values from the connection string. If ApplyURI is called
+// after SetAuth, then its values will overwrite those from SetAuth.
+func Connect(opts ...*options.ClientOptions) (*Client, error) {
+ c, err := newClient(opts...)
+ if err != nil {
+ return nil, err
+ }
+ err = c.connect()
+ if err != nil {
+ return nil, err
+ }
+ return c, nil
+}
+
+// newClient creates a new client to connect to a deployment specified by the uri.
+//
+// When creating an options.ClientOptions, the order the methods are called matters. Later Set*
+// methods will overwrite the values from previous Set* method invocations. This includes the
+// ApplyURI method. This allows callers to determine the order of precedence for option
+// application. For instance, if ApplyURI is called before SetAuth, the Credential from
+// SetAuth will overwrite the values from the connection string. If ApplyURI is called
+// after SetAuth, then its values will overwrite those from SetAuth.
+//
+// The opts parameter is processed using options.MergeClientOptions, which will overwrite entire
+// option fields of previous options, there is no partial overwriting. For example, if Username is
+// set in the Auth field for the first option, and Password is set for the second but with no
+// Username, after the merge the Username field will be empty.
+func newClient(opts ...*options.ClientOptions) (*Client, error) {
+ clientOpts := options.MergeClientOptions(opts...)
+
+ id, err := uuid.New()
+ if err != nil {
+ return nil, err
+ }
+
+ client := &Client{
+ id: id,
+ currentDriverInfo: &atomic.Pointer[options.DriverInfo]{},
+ }
+
+ // ClusterClock
+ client.clock = new(session.ClusterClock)
+
+ // LocalThreshold
+ client.localThreshold = defaultLocalThreshold
+ if clientOpts.LocalThreshold != nil {
+ client.localThreshold = *clientOpts.LocalThreshold
+ }
+ // Monitor
+ if clientOpts.Monitor != nil {
+ client.monitor = clientOpts.Monitor
+ }
+ // ServerMonitor
+ if clientOpts.ServerMonitor != nil {
+ client.serverMonitor = clientOpts.ServerMonitor
+ }
+ // ReadConcern
+ client.readConcern = &readconcern.ReadConcern{}
+ if clientOpts.ReadConcern != nil {
+ client.readConcern = clientOpts.ReadConcern
+ }
+ // ReadPreference
+ client.readPreference = readpref.Primary()
+ if clientOpts.ReadPreference != nil {
+ client.readPreference = clientOpts.ReadPreference
+ }
+ // BSONOptions
+ if clientOpts.BSONOptions != nil {
+ client.bsonOpts = clientOpts.BSONOptions
+ }
+ // Registry
+ client.registry = defaultRegistry
+ if clientOpts.Registry != nil {
+ client.registry = clientOpts.Registry
+ }
+ // RetryWrites
+ client.retryWrites = true // retry writes on by default
+ if clientOpts.RetryWrites != nil {
+ client.retryWrites = *clientOpts.RetryWrites
+ }
+ client.retryReads = true
+ if clientOpts.RetryReads != nil {
+ client.retryReads = *clientOpts.RetryReads
+ }
+ client.maxAdaptiveRetries = clientOpts.MaxAdaptiveRetries
+ client.enableOverloadRetargeting = clientOpts.EnableOverloadRetargeting != nil &&
+ *clientOpts.EnableOverloadRetargeting
+ // Timeout
+ client.timeout = clientOpts.Timeout
+ client.httpClient = clientOpts.HTTPClient
+ // WriteConcern
+ if clientOpts.WriteConcern != nil {
+ client.writeConcern = clientOpts.WriteConcern
+ }
+ // AutoEncryptionOptions
+ if clientOpts.AutoEncryptionOptions != nil {
+ client.isAutoEncryptionSet = true
+ if err := client.configureAutoEncryption(clientOpts); err != nil {
+ return nil, err
+ }
+ } else {
+ client.cryptFLE = clientOpts.Crypt
+ }
+
+ // Deployment
+ if clientOpts.Deployment != nil {
+ client.deployment = clientOpts.Deployment
+ }
+
+ // Set default options
+ if clientOpts.MaxPoolSize == nil {
+ defaultMaxPoolSize := uint64(defaultMaxPoolSize)
+ clientOpts.MaxPoolSize = &defaultMaxPoolSize
+ }
+
+ if clientOpts.Auth != nil {
+ client.authenticator, err = auth.CreateAuthenticator(
+ clientOpts.Auth.AuthMechanism,
+ topology.ConvertCreds(clientOpts.Auth),
+ clientOpts.HTTPClient,
+ )
+ if err != nil {
+ return nil, fmt.Errorf("error creating authenticator: %w", err)
+ }
+ }
+
+ if clientOpts.DriverInfo != nil {
+ client.AppendDriverInfo(*clientOpts.DriverInfo)
+ }
+
+ cfg, err := topology.NewAuthenticatorConfig(client.authenticator,
+ topology.WithAuthConfigClock(client.clock),
+ topology.WithAuthConfigClientOptions(clientOpts),
+ topology.WithAuthConfigDriverInfo(client.currentDriverInfo),
+ )
+ if err != nil {
+ return nil, err
+ }
+
+ var connectTimeout time.Duration
+ if clientOpts.ConnectTimeout != nil {
+ connectTimeout = *clientOpts.ConnectTimeout
+ }
+
+ client.serverAPI = topology.ServerAPIFromServerOptions(connectTimeout, cfg.ServerOpts)
+
+ if client.deployment == nil {
+ client.deployment, err = topology.New(cfg)
+ if err != nil {
+ return nil, wrapErrors(err)
+ }
+ }
+
+ // Create a logger for the client.
+ client.logger, err = newLogger(clientOpts.LoggerOptions)
+ if err != nil {
+ return nil, fmt.Errorf("invalid logger options: %w", err)
+ }
+
+ return client, nil
+}
+
+// Connect initializes the Client by starting background monitoring goroutines.
+// If the Client was created using the NewClient function, this method must be called before a Client can be used.
+//
+// Connect starts background goroutines to monitor the state of the deployment and does not do any I/O in the main
+// goroutine. The Client.Ping method can be used to verify that the connection was created successfully.
+func (c *Client) connect() error {
+ if connector, ok := c.deployment.(driver.Connector); ok {
+ err := connector.Connect()
+ if err != nil {
+ return wrapErrors(err)
+ }
+ }
+
+ if c.mongocryptdFLE != nil {
+ if err := c.mongocryptdFLE.connect(); err != nil {
+ return err
+ }
+ }
+
+ if c.internalClientFLE != nil {
+ if err := c.internalClientFLE.connect(); err != nil {
+ return err
+ }
+ }
+
+ if c.keyVaultClientFLE != nil && c.keyVaultClientFLE != c.internalClientFLE && c.keyVaultClientFLE != c {
+ if err := c.keyVaultClientFLE.connect(); err != nil {
+ return err
+ }
+ }
+
+ if c.metadataClientFLE != nil && c.metadataClientFLE != c.internalClientFLE && c.metadataClientFLE != c {
+ if err := c.metadataClientFLE.connect(); err != nil {
+ return err
+ }
+ }
+
+ var updateChan <-chan description.Topology
+ if subscriber, ok := c.deployment.(driver.Subscriber); ok {
+ sub, err := subscriber.Subscribe()
+ if err != nil {
+ return wrapErrors(err)
+ }
+ updateChan = sub.Updates
+ }
+ c.sessionPool = session.NewPool(updateChan)
+ return nil
+}
+
+// AppendDriverInfo appends the provided [options.DriverInfo] to the metadata
+// (e.g. name, version, platform) that will be sent to the server in handshake
+// requests when establishing new connections.
+//
+// Repeated calls to AppendDriverInfo with equivalent DriverInfo is a no-op.
+//
+// Metadata is limited to 512 bytes; any excess will be truncated.
+func (c *Client) AppendDriverInfo(info options.DriverInfo) {
+ if _, loaded := c.seenDriverInfo.LoadOrStore(info, struct{}{}); loaded {
+ return
+ }
+
+ if old := c.currentDriverInfo.Load(); old != nil {
+ if old.Name != "" && info.Name != "" && old.Name != info.Name {
+ info.Name = old.Name + "|" + info.Name
+ } else if old.Name != "" {
+ info.Name = old.Name
+ }
+
+ if old.Version != "" && info.Version != "" && old.Version != info.Version {
+ info.Version = old.Version + "|" + info.Version
+ } else if old.Version != "" {
+ info.Version = old.Version
+ }
+
+ if old.Platform != "" && info.Platform != "" && old.Platform != info.Platform {
+ info.Platform = old.Platform + "|" + info.Platform
+ } else if old.Platform != "" {
+ info.Platform = old.Platform
+ }
+ }
+
+ // Copy-on-write so that the info stored in the client is immutable.
+ infoCopy := new(options.DriverInfo)
+ *infoCopy = info
+
+ c.currentDriverInfo.Store(infoCopy)
+}
+
+// Disconnect closes sockets to the topology referenced by this Client. It will
+// shut down any monitoring goroutines, close the idle connection pool, and will
+// wait until all the in use connections have been returned to the connection
+// pool and closed before returning. If the context expires via cancellation,
+// deadline, or timeout before the in use connections have returned, the in use
+// connections will be closed, resulting in the failure of any in flight read
+// or write operations. If this method returns with no errors, all connections
+// associated with this Client have been closed.
+func (c *Client) Disconnect(ctx context.Context) error {
+ if c.logger != nil {
+ defer c.logger.Close()
+ }
+
+ if ctx == nil {
+ ctx = context.Background()
+ }
+
+ if c.httpClient == httputil.DefaultHTTPClient {
+ defer httputil.CloseIdleHTTPConnections(c.httpClient)
+ }
+
+ c.endSessions(ctx)
+ if c.mongocryptdFLE != nil {
+ if err := c.mongocryptdFLE.disconnect(ctx); err != nil {
+ return err
+ }
+ }
+
+ if c.internalClientFLE != nil {
+ if err := c.internalClientFLE.Disconnect(ctx); err != nil {
+ return err
+ }
+ }
+
+ if c.keyVaultClientFLE != nil && c.keyVaultClientFLE != c.internalClientFLE && c.keyVaultClientFLE != c {
+ if err := c.keyVaultClientFLE.Disconnect(ctx); err != nil {
+ return err
+ }
+ }
+ if c.metadataClientFLE != nil && c.metadataClientFLE != c.internalClientFLE && c.metadataClientFLE != c {
+ if err := c.metadataClientFLE.Disconnect(ctx); err != nil {
+ return err
+ }
+ }
+ if c.cryptFLE != nil {
+ c.cryptFLE.Close()
+ }
+
+ if disconnector, ok := c.deployment.(driver.Disconnector); ok {
+ return wrapErrors(disconnector.Disconnect(ctx))
+ }
+
+ return nil
+}
+
+// Ping sends a ping command to verify that the client can connect to the deployment.
+//
+// The rp parameter is used to determine which server is selected for the operation.
+// If it is nil, the client's read preference is used.
+//
+// If the server is down, Ping will try to select a server until the client's server selection timeout expires.
+// This can be configured through the ClientOptions.SetServerSelectionTimeout option when creating a new Client.
+// After the timeout expires, a server selection error is returned.
+//
+// Using Ping reduces application resilience because applications starting up will error if the server is temporarily
+// unavailable or is failing over (e.g. during autoscaling due to a load spike).
+func (c *Client) Ping(ctx context.Context, rp *readpref.ReadPref) error {
+ if ctx == nil {
+ ctx = context.Background()
+ }
+
+ if rp == nil {
+ rp = c.readPreference
+ }
+
+ db := c.Database("admin")
+ res := db.RunCommand(ctx, bson.D{
+ {"ping", 1},
+ }, options.RunCmd().SetReadPreference(rp))
+
+ return wrapErrors(res.Err())
+}
+
+// StartSession starts a new session configured with the given options.
+//
+// StartSession does not actually communicate with the server and will not error if the client is
+// disconnected.
+//
+// StartSession is safe to call from multiple goroutines concurrently. However, Sessions returned by StartSession are
+// not safe for concurrent use by multiple goroutines.
+//
+// If the DefaultReadConcern, DefaultWriteConcern, or DefaultReadPreference options are not set, the client's read
+// concern, write concern, or read preference will be used, respectively.
+func (c *Client) StartSession(opts ...options.Lister[options.SessionOptions]) (*Session, error) {
+ sessArgs, err := mongoutil.NewOptions(opts...)
+ if err != nil {
+ return nil, err
+ }
+ if sessArgs.CausalConsistency == nil && (sessArgs.Snapshot == nil || !*sessArgs.Snapshot) {
+ sessArgs.CausalConsistency = &options.DefaultCausalConsistency
+ }
+ coreOpts := &session.ClientOptions{
+ DefaultReadConcern: c.readConcern,
+ DefaultReadPreference: c.readPreference,
+ DefaultWriteConcern: c.writeConcern,
+ }
+ if sessArgs.CausalConsistency != nil {
+ coreOpts.CausalConsistency = sessArgs.CausalConsistency
+ }
+ if bldr := sessArgs.DefaultTransactionOptions; bldr != nil {
+ txnOpts, err := mongoutil.NewOptions[options.TransactionOptions](bldr)
+ if err != nil {
+ return nil, err
+ }
+
+ if rc := txnOpts.ReadConcern; rc != nil {
+ coreOpts.DefaultReadConcern = rc
+ }
+
+ if wc := txnOpts.WriteConcern; wc != nil {
+ coreOpts.DefaultWriteConcern = wc
+ }
+
+ if rp := txnOpts.ReadPreference; rp != nil {
+ coreOpts.DefaultReadPreference = rp
+ }
+ }
+
+ if sessArgs.Snapshot != nil {
+ coreOpts.Snapshot = sessArgs.Snapshot
+ }
+
+ if sessArgs.SnapshotTime != nil {
+ coreOpts.SnapshotTime = sessArgs.SnapshotTime
+ }
+
+ sess, err := session.NewClientSession(c.sessionPool, c.id, coreOpts)
+ if err != nil {
+ return nil, wrapErrors(err)
+ }
+
+ return &Session{
+ clientSession: sess,
+ client: c,
+ deployment: c.deployment,
+ }, nil
+}
+
+func (c *Client) endSessions(ctx context.Context) {
+ sessionIDs := c.sessionPool.IDSlice()
+ op := operation.NewEndSessions(nil).ClusterClock(c.clock).Deployment(c.deployment).
+ ServerSelector(&serverselector.ReadPref{ReadPref: readpref.PrimaryPreferred()}).
+ CommandMonitor(c.monitor).Database("admin").Crypt(c.cryptFLE).ServerAPI(c.serverAPI).
+ MaxAdaptiveRetries(c.effectiveAdaptiveRetries(true)).
+ EnableOverloadRetargeting(c.enableOverloadRetargeting)
+
+ totalNumIDs := len(sessionIDs)
+ var currentBatch []bsoncore.Document
+ for i := 0; i < totalNumIDs; i++ {
+ currentBatch = append(currentBatch, sessionIDs[i])
+
+ // If we are at the end of a batch or the end of the overall IDs array, execute the operation.
+ if ((i+1)%endSessionsBatchSize) == 0 || i == totalNumIDs-1 {
+ // Ignore all errors when ending sessions.
+ _, marshalVal, err := bson.MarshalValue(currentBatch)
+ if err == nil {
+ _ = op.SessionIDs(marshalVal).Execute(ctx)
+ }
+
+ currentBatch = currentBatch[:0]
+ }
+ }
+}
+
+func (c *Client) configureAutoEncryption(args *options.ClientOptions) error {
+ c.encryptedFieldsMap = args.AutoEncryptionOptions.EncryptedFieldsMap
+ if err := c.configureKeyVaultClientFLE(args); err != nil {
+ return err
+ }
+
+ if err := c.configureMetadataClientFLE(args); err != nil {
+ return err
+ }
+
+ mc, err := c.newMongoCrypt(args.AutoEncryptionOptions)
+ if err != nil {
+ return err
+ }
+
+ // If the crypt_shared library was not loaded, try to spawn and connect to mongocryptd.
+ if mc.CryptSharedLibVersionString() == "" {
+ mongocryptdFLE, err := newMongocryptdClient(args.AutoEncryptionOptions)
+ if err != nil {
+ return err
+ }
+ c.mongocryptdFLE = mongocryptdFLE
+ }
+
+ c.configureCryptFLE(mc, args.AutoEncryptionOptions)
+ return nil
+}
+
+func (c *Client) getOrCreateInternalClient(args *options.ClientOptions) (*Client, error) {
+ if c.internalClientFLE != nil {
+ return c.internalClientFLE, nil
+ }
+
+ argsCopy := *args
+
+ argsCopy.AutoEncryptionOptions = nil
+ argsCopy.MinPoolSize = ptrutil.Ptr[uint64](0)
+
+ var err error
+ c.internalClientFLE, err = newClient(&argsCopy)
+
+ return c.internalClientFLE, err
+}
+
+func (c *Client) configureKeyVaultClientFLE(clientOpts *options.ClientOptions) error {
+ aeOpts := clientOpts.AutoEncryptionOptions
+
+ var err error
+
+ switch {
+ case aeOpts.KeyVaultClientOptions != nil:
+ c.keyVaultClientFLE, err = newClient(aeOpts.KeyVaultClientOptions)
+ case clientOpts.MaxPoolSize != nil && *clientOpts.MaxPoolSize == 0:
+ c.keyVaultClientFLE = c
+ default:
+ c.keyVaultClientFLE, err = c.getOrCreateInternalClient(clientOpts)
+ }
+
+ if err != nil {
+ return err
+ }
+
+ dbName, collName := splitNamespace(aeOpts.KeyVaultNamespace)
+ c.keyVaultCollFLE = c.keyVaultClientFLE.Database(dbName).Collection(collName, keyVaultCollOpts)
+ return nil
+}
+
+func (c *Client) configureMetadataClientFLE(clientOpts *options.ClientOptions) error {
+ aeOpts := clientOpts.AutoEncryptionOptions
+
+ if aeOpts.BypassAutoEncryption != nil && *aeOpts.BypassAutoEncryption {
+ // no need for a metadata client.
+ return nil
+ }
+ if clientOpts.MaxPoolSize != nil && *clientOpts.MaxPoolSize == 0 {
+ c.metadataClientFLE = c
+ return nil
+ }
+
+ var err error
+ c.metadataClientFLE, err = c.getOrCreateInternalClient(clientOpts)
+
+ return err
+}
+
+func (c *Client) newMongoCrypt(opts *options.AutoEncryptionOptions) (*mongocrypt.MongoCrypt, error) {
+ // convert schemas in SchemaMap to bsoncore documents
+ cryptSchemaMap := make(map[string]bsoncore.Document)
+ for k, v := range opts.SchemaMap {
+ schema, err := marshal(v, c.bsonOpts, c.registry)
+ if err != nil {
+ return nil, err
+ }
+ cryptSchemaMap[k] = schema
+ }
+
+ // convert schemas in EncryptedFieldsMap to bsoncore documents
+ cryptEncryptedFieldsMap := make(map[string]bsoncore.Document)
+ for k, v := range opts.EncryptedFieldsMap {
+ encryptedFields, err := marshal(v, c.bsonOpts, c.registry)
+ if err != nil {
+ return nil, err
+ }
+ cryptEncryptedFieldsMap[k] = encryptedFields
+ }
+
+ kmsProviders, err := marshal(opts.KmsProviders, c.bsonOpts, c.registry)
+ if err != nil {
+ return nil, fmt.Errorf("error creating KMS providers document: %w", err)
+ }
+
+ // Set the crypt_shared library override path from the "cryptSharedLibPath" extra option if one
+ // was set.
+ cryptSharedLibPath := ""
+ if val, ok := opts.ExtraOptions["cryptSharedLibPath"]; ok {
+ str, ok := val.(string)
+ if !ok {
+ return nil, fmt.Errorf(
+ `expected AutoEncryption extra option "cryptSharedLibPath" to be a string, but is a %T`, val)
+ }
+ cryptSharedLibPath = str
+ }
+
+ // Explicitly disable loading the crypt_shared library if requested. Note that this is ONLY
+ // intended for use from tests; there is no supported public API for explicitly disabling
+ // loading the crypt_shared library.
+ cryptSharedLibDisabled := false
+ if v, ok := opts.ExtraOptions["__cryptSharedLibDisabledForTestOnly"]; ok {
+ cryptSharedLibDisabled = v.(bool)
+ }
+
+ bypassAutoEncryption := opts.BypassAutoEncryption != nil && *opts.BypassAutoEncryption
+ bypassQueryAnalysis := opts.BypassQueryAnalysis != nil && *opts.BypassQueryAnalysis
+
+ mc, err := mongocrypt.NewMongoCrypt(&mcopts.MongoCryptOptions{
+ KmsProviders: kmsProviders,
+ LocalSchemaMap: cryptSchemaMap,
+ BypassQueryAnalysis: bypassQueryAnalysis,
+ EncryptedFieldsMap: cryptEncryptedFieldsMap,
+ CryptSharedLibDisabled: cryptSharedLibDisabled || bypassAutoEncryption,
+ CryptSharedLibOverridePath: cryptSharedLibPath,
+ HTTPClient: opts.HTTPClient,
+ KeyExpiration: opts.KeyExpiration,
+ })
+ if err != nil {
+ return nil, err
+ }
+
+ var cryptSharedLibRequired bool
+ if val, ok := opts.ExtraOptions["cryptSharedLibRequired"]; ok {
+ b, ok := val.(bool)
+ if !ok {
+ return nil, fmt.Errorf(
+ `expected AutoEncryption extra option "cryptSharedLibRequired" to be a bool, but is a %T`, val)
+ }
+ cryptSharedLibRequired = b
+ }
+
+ // If the "cryptSharedLibRequired" extra option is set to true, check the MongoCrypt version
+ // string to confirm that the library was successfully loaded. If the version string is empty,
+ // return an error indicating that we couldn't load the crypt_shared library.
+ if cryptSharedLibRequired && mc.CryptSharedLibVersionString() == "" {
+ return nil, errors.New(
+ `AutoEncryption extra option "cryptSharedLibRequired" is true, but we failed to load the crypt_shared library`)
+ }
+
+ return mc, nil
+}
+
+//nolint:unused // the unused linter thinks that this function is unreachable because "c.newMongoCrypt" always panics without the "cse" build tag set.
+func (c *Client) configureCryptFLE(mc *mongocrypt.MongoCrypt, opts *options.AutoEncryptionOptions) {
+ bypass := opts.BypassAutoEncryption != nil && *opts.BypassAutoEncryption
+ kr := keyRetriever{coll: c.keyVaultCollFLE}
+ var cir collInfoRetriever
+ // If bypass is true, c.metadataClientFLE is nil and the collInfoRetriever
+ // will not be used. If bypass is false, to the parent client or the
+ // internal client.
+ if !bypass {
+ cir = collInfoRetriever{client: c.metadataClientFLE}
+ }
+
+ c.cryptFLE = driver.NewCrypt(&driver.CryptOptions{
+ MongoCrypt: mc,
+ CollInfoFn: cir.cryptCollInfo,
+ KeyFn: kr.cryptKeys,
+ MarkFn: c.mongocryptdFLE.markCommand,
+ TLSConfig: opts.TLSConfig,
+ BypassAutoEncryption: bypass,
+ })
+}
+
+// validSession returns an error if the session doesn't belong to the client
+func (c *Client) validSession(sess *session.Client) error {
+ if sess != nil && sess.ClientID != c.id {
+ return ErrWrongClient
+ }
+ return nil
+}
+
+// Database returns a handle for a database with the given name configured with the given DatabaseOptions.
+func (c *Client) Database(name string, opts ...options.Lister[options.DatabaseOptions]) *Database {
+ return newDatabase(c, name, opts...)
+}
+
+// ListDatabases executes a listDatabases command and returns the result.
+//
+// The filter parameter must be a document containing query operators and can be used to select which
+// databases are included in the result. It cannot be nil. An empty document (e.g. bson.D{}) should be used to include
+// all databases.
+//
+// The opts parameter can be used to specify options for this operation (see the options.ListDatabasesOptions documentation).
+//
+// For more information about the command, see https://www.mongodb.com/docs/manual/reference/command/listDatabases/.
+func (c *Client) ListDatabases(ctx context.Context, filter any, opts ...options.Lister[options.ListDatabasesOptions]) (ListDatabasesResult, error) {
+ if ctx == nil {
+ ctx = context.Background()
+ }
+
+ sess := sessionFromContext(ctx)
+
+ err := c.validSession(sess)
+ if err != nil {
+ return ListDatabasesResult{}, err
+ }
+ if sess == nil && c.sessionPool != nil {
+ sess = session.NewImplicitClientSession(c.sessionPool, c.id)
+ defer sess.EndSession()
+ }
+
+ err = c.validSession(sess)
+ if err != nil {
+ return ListDatabasesResult{}, err
+ }
+
+ filterDoc, err := marshal(filter, c.bsonOpts, c.registry)
+ if err != nil {
+ return ListDatabasesResult{}, err
+ }
+
+ retry := driver.RetryNone
+ if c.retryReads {
+ retry = driver.RetryOncePerCommand
+ }
+
+ maxAdaptiveRetries := c.effectiveAdaptiveRetries(c.retryReads)
+
+ var selector description.ServerSelector
+
+ selector = &serverselector.Composite{
+ Selectors: []description.ServerSelector{
+ &serverselector.ReadPref{ReadPref: readpref.Primary()},
+ &serverselector.Latency{Latency: c.localThreshold},
+ },
+ }
+
+ selector = makeReadPrefSelector(sess, selector, c.localThreshold)
+
+ lda, err := mongoutil.NewOptions(opts...)
+ if err != nil {
+ return ListDatabasesResult{}, err
+ }
+ op := operation.NewListDatabases(filterDoc).
+ Session(sess).ReadPreference(c.readPreference).CommandMonitor(c.monitor).
+ Retry(retry).MaxAdaptiveRetries(maxAdaptiveRetries).
+ EnableOverloadRetargeting(c.enableOverloadRetargeting).
+ ServerSelector(selector).ClusterClock(c.clock).Database("admin").Deployment(c.deployment).Crypt(c.cryptFLE).
+ ServerAPI(c.serverAPI).Timeout(c.timeout).Authenticator(c.authenticator)
+
+ if lda.NameOnly != nil {
+ op = op.NameOnly(*lda.NameOnly)
+ }
+ if lda.AuthorizedDatabases != nil {
+ op = op.AuthorizedDatabases(*lda.AuthorizedDatabases)
+ }
+
+ err = op.Execute(ctx)
+ if err != nil {
+ return ListDatabasesResult{}, wrapErrors(err)
+ }
+
+ return newListDatabasesResultFromOperation(op.Result()), nil
+}
+
+// ListDatabaseNames executes a listDatabases command and returns a slice containing the names of all of the databases
+// on the server.
+//
+// The filter parameter must be a document containing query operators and can be used to select which databases
+// are included in the result. It cannot be nil. An empty document (e.g. bson.D{}) should be used to include all
+// databases.
+//
+// The opts parameter can be used to specify options for this operation (see the options.ListDatabasesOptions
+// documentation.)
+//
+// For more information about the command, see https://www.mongodb.com/docs/manual/reference/command/listDatabases/.
+func (c *Client) ListDatabaseNames(ctx context.Context, filter any, opts ...options.Lister[options.ListDatabasesOptions]) ([]string, error) {
+ opts = append(opts, options.ListDatabases().SetNameOnly(true))
+
+ res, err := c.ListDatabases(ctx, filter, opts...)
+ if err != nil {
+ return nil, err
+ }
+
+ names := make([]string, 0)
+ for _, spec := range res.Databases {
+ names = append(names, spec.Name)
+ }
+
+ return names, nil
+}
+
+// WithSession creates a new session context from the ctx and sess parameters
+// and uses it to call the fn callback.
+//
+// WithSession is safe to call from multiple goroutines concurrently. However,
+// the context passed to the WithSession callback function is not safe for
+// concurrent use by multiple goroutines.
+//
+// If the ctx parameter already contains a Session, that Session will be
+// replaced with the one provided.
+//
+// Any error returned by the fn callback will be returned without any
+// modifications.
+func WithSession(ctx context.Context, sess *Session, fn func(context.Context) error) error {
+ return fn(NewSessionContext(ctx, sess))
+}
+
+// UseSession creates a new Session and uses it to create a new session context,
+// which is used to call the fn callback. After the callback returns, the
+// created Session is ended, meaning that any in-progress transactions started
+// by fn will be aborted even if fn returns an error.
+//
+// UseSession is safe to call from multiple goroutines concurrently. However,
+// the context passed to the UseSession callback function is not safe for
+// concurrent use by multiple goroutines.
+//
+// If the ctx parameter already contains a Session, that Session will be
+// replaced with the newly created one.
+//
+// Any error returned by the fn callback will be returned without any
+// modifications.
+func (c *Client) UseSession(ctx context.Context, fn func(context.Context) error) error {
+ return c.UseSessionWithOptions(ctx, options.Session(), fn)
+}
+
+// UseSessionWithOptions operates like UseSession but uses the given
+// SessionOptions to create the Session.
+//
+// UseSessionWithOptions is safe to call from multiple goroutines concurrently.
+// However, the context passed to the UseSessionWithOptions callback function is
+// not safe for concurrent use by multiple goroutines.
+func (c *Client) UseSessionWithOptions(
+ ctx context.Context,
+ opts *options.SessionOptionsBuilder,
+ fn func(context.Context) error,
+) error {
+ defaultSess, err := c.StartSession(opts)
+ if err != nil {
+ return err
+ }
+
+ defer defaultSess.EndSession(ctx)
+ return fn(NewSessionContext(ctx, defaultSess))
+}
+
+// Watch returns a change stream for all changes on the deployment. See
+// https://www.mongodb.com/docs/manual/changeStreams/ for more information about change streams.
+//
+// The client must be configured with read concern majority or no read concern for a change stream to be created
+// successfully.
+//
+// The pipeline parameter must be an array of documents, each representing a pipeline stage. The pipeline cannot be
+// nil or empty. The stage documents must all be non-nil. See https://www.mongodb.com/docs/manual/changeStreams/ for a list
+// of pipeline stages that can be used with change streams. For a pipeline of bson.D documents, the mongo.Pipeline{}
+// type can be used.
+//
+// The opts parameter can be used to specify options for change stream creation (see the options.ChangeStreamOptions
+// documentation).
+func (c *Client) Watch(ctx context.Context, pipeline any,
+ opts ...options.Lister[options.ChangeStreamOptions],
+) (*ChangeStream, error) {
+ csConfig := changeStreamConfig{
+ readConcern: c.readConcern,
+ readPreference: c.readPreference,
+ client: c,
+ bsonOpts: c.bsonOpts,
+ registry: c.registry,
+ streamType: ClientStream,
+ crypt: c.cryptFLE,
+ }
+
+ return newChangeStream(ctx, csConfig, pipeline, opts...)
+}
+
+// NumberSessionsInProgress returns the number of sessions that have been started for this client but have not been
+// closed (i.e. EndSession has not been called).
+func (c *Client) NumberSessionsInProgress() int {
+ // The underlying session pool uses an int64 for checkedOut to allow atomic
+ // access. We convert to an int here to maintain backward compatibility with
+ // older versions of the driver that did not atomically access checkedOut.
+ return int(c.sessionPool.CheckedOut())
+}
+
+func (c *Client) createBaseCursorOptions(retryOverload bool) driver.CursorOptions {
+ return driver.CursorOptions{
+ CommandMonitor: c.monitor,
+ Crypt: c.cryptFLE,
+ ServerAPI: c.serverAPI,
+ MaxAdaptiveRetries: c.effectiveAdaptiveRetries(retryOverload),
+ EnableOverloadRetargeting: c.enableOverloadRetargeting,
+ }
+}
+
+func (c *Client) effectiveAdaptiveRetries(retryOverload bool) uint {
+ if !retryOverload {
+ return 0
+ }
+ if c.maxAdaptiveRetries != nil {
+ return *c.maxAdaptiveRetries
+ }
+ return defaultAdaptiveRetries
+}
+
+// ClientBulkWrite is a struct that can be used in a client-level BulkWrite operation.
+type ClientBulkWrite struct {
+ Database string
+ Collection string
+ Model ClientWriteModel
+}
+
+// BulkWrite performs a client-level bulk write operation.
+func (c *Client) BulkWrite(ctx context.Context, writes []ClientBulkWrite,
+ opts ...options.Lister[options.ClientBulkWriteOptions],
+) (*ClientBulkWriteResult, error) {
+ // TODO(GODRIVER-3403): Remove after support for QE with Client.bulkWrite.
+ if c.isAutoEncryptionSet {
+ return nil, errors.New("bulkWrite does not currently support automatic encryption")
+ }
+
+ if len(writes) == 0 {
+ return nil, fmt.Errorf("invalid writes: %w", ErrEmptySlice)
+ }
+ bwo, err := mongoutil.NewOptions(opts...)
+ if err != nil {
+ return nil, err
+ }
+
+ if ctx == nil {
+ ctx = context.Background()
+ }
+
+ sess := sessionFromContext(ctx)
+ if sess == nil && c.sessionPool != nil {
+ sess = session.NewImplicitClientSession(c.sessionPool, c.id)
+ defer sess.EndSession()
+ }
+
+ err = c.validSession(sess)
+ if err != nil {
+ return nil, err
+ }
+
+ transactionRunning := sess.TransactionRunning()
+ wc := c.writeConcern
+ if transactionRunning {
+ wc = nil
+ }
+ if bwo.WriteConcern != nil {
+ if transactionRunning {
+ return nil, errors.New("cannot set write concern after starting a transaction")
+ }
+ wc = bwo.WriteConcern
+ }
+ acknowledged := wc.Acknowledged()
+ if !acknowledged {
+ if bwo.Ordered == nil || *bwo.Ordered {
+ return nil, errors.New("cannot request unacknowledged write concern and ordered writes")
+ }
+ sess = nil
+ }
+
+ maxAdaptiveRetries := c.effectiveAdaptiveRetries(c.retryWrites)
+
+ writeSelector := &serverselector.Composite{
+ Selectors: []description.ServerSelector{
+ &serverselector.Write{},
+ &serverselector.Latency{Latency: c.localThreshold},
+ },
+ }
+ selector := makePinnedSelector(sess, writeSelector)
+
+ writePairs := make([]clientBulkWritePair, len(writes))
+ for i, w := range writes {
+ writePairs[i] = clientBulkWritePair{
+ namespace: fmt.Sprintf("%s.%s", w.Database, w.Collection),
+ model: w.Model,
+ }
+ }
+
+ op := clientBulkWrite{
+ writePairs: writePairs,
+ ordered: bwo.Ordered,
+ bypassDocumentValidation: bwo.BypassDocumentValidation,
+ comment: bwo.Comment,
+ let: bwo.Let,
+ session: sess,
+ client: c,
+ selector: selector,
+ writeConcern: wc,
+
+ maxAdaptiveRetries: maxAdaptiveRetries,
+ enableOverloadRetargeting: c.enableOverloadRetargeting,
+ }
+ if rawData, ok := optionsutil.Value(bwo.Internal, "rawData").(bool); ok {
+ op.rawData = &rawData
+ }
+ if additionalCmd, ok := optionsutil.Value(bwo.Internal, "addCommandFields").(bson.D); ok {
+ op.additionalCmd = additionalCmd
+ }
+ if bwo.VerboseResults == nil || !(*bwo.VerboseResults) {
+ op.errorsOnly = true
+ } else if !acknowledged {
+ return nil, errors.New("cannot request unacknowledged write concern and verbose results")
+ }
+ op.result.Acknowledged = acknowledged
+ op.result.HasVerboseResults = !op.errorsOnly
+ err = op.execute(ctx)
+ return &op.result, wrapErrors(err)
+}
+
+// newLogger will use the LoggerOptions to create an internal logger and publish
+// messages using a LogSink.
+func newLogger(opts *options.LoggerOptions) (*logger.Logger, error) {
+ // If there are no logger options, then create a default logger.
+ if opts == nil {
+ opts = options.Logger()
+ }
+
+ // If there are no component-level options and the environment does not
+ // contain component variables, then do nothing.
+ if len(opts.ComponentLevels) == 0 && !logger.EnvHasComponentVariables() {
+ return nil, nil
+ }
+
+ // Otherwise, collect the component-level options and create a logger.
+ componentLevels := make(map[logger.Component]logger.Level)
+ for component, level := range opts.ComponentLevels {
+ componentLevels[logger.Component(component)] = logger.Level(level)
+ }
+
+ return logger.New(opts.Sink, opts.MaxDocumentLength, componentLevels)
+}
diff --git a/vendor/go.mongodb.org/mongo-driver/v2/mongo/client_bulk_write.go b/vendor/go.mongodb.org/mongo-driver/v2/mongo/client_bulk_write.go
new file mode 100644
index 0000000..cdb5265
--- /dev/null
+++ b/vendor/go.mongodb.org/mongo-driver/v2/mongo/client_bulk_write.go
@@ -0,0 +1,737 @@
+// Copyright (C) MongoDB, Inc. 2024-present.
+//
+// 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
+
+package mongo
+
+import (
+ "context"
+ "errors"
+ "fmt"
+ "io"
+ "strconv"
+
+ "go.mongodb.org/mongo-driver/v2/bson"
+ "go.mongodb.org/mongo-driver/v2/internal/driverutil"
+ "go.mongodb.org/mongo-driver/v2/mongo/options"
+ "go.mongodb.org/mongo-driver/v2/mongo/writeconcern"
+ "go.mongodb.org/mongo-driver/v2/x/bsonx/bsoncore"
+ "go.mongodb.org/mongo-driver/v2/x/mongo/driver"
+ "go.mongodb.org/mongo-driver/v2/x/mongo/driver/description"
+ "go.mongodb.org/mongo-driver/v2/x/mongo/driver/session"
+ "go.mongodb.org/mongo-driver/v2/x/mongo/driver/wiremessage"
+)
+
+const (
+ database = "admin"
+)
+
+type clientBulkWritePair struct {
+ namespace string
+ model any
+}
+
+type clientBulkWrite struct {
+ writePairs []clientBulkWritePair
+ errorsOnly bool
+ ordered *bool
+ bypassDocumentValidation *bool
+ comment any
+ let any
+ session *session.Client
+ client *Client
+ selector description.ServerSelector
+ writeConcern *writeconcern.WriteConcern
+ rawData *bool
+ additionalCmd bson.D
+
+ maxAdaptiveRetries uint
+ enableOverloadRetargeting bool
+
+ result ClientBulkWriteResult
+}
+
+func (bw *clientBulkWrite) execute(ctx context.Context) error {
+ if len(bw.writePairs) == 0 {
+ return fmt.Errorf("invalid writes: %w", ErrEmptySlice)
+ }
+ for i, m := range bw.writePairs {
+ if m.model == nil {
+ return fmt.Errorf("error from model at index %d: %w", i, ErrNilDocument)
+ }
+ }
+ batches := &modelBatches{
+ session: bw.session,
+ client: bw.client,
+ ordered: bw.ordered == nil || *bw.ordered,
+ writePairs: bw.writePairs,
+ result: &bw.result,
+ retryMode: driver.RetryOnce,
+ }
+ err := driver.Operation{
+ CommandFn: bw.newCommand(),
+ ProcessResponseFn: batches.processResponse,
+ Client: bw.session,
+ Clock: bw.client.clock,
+ RetryMode: &batches.retryMode,
+ MaxAdaptiveRetries: bw.maxAdaptiveRetries,
+ EnableOverloadRetargeting: bw.enableOverloadRetargeting,
+ Type: driver.Write,
+ Batches: batches,
+ CommandMonitor: bw.client.monitor,
+ Database: database,
+ Deployment: bw.client.deployment,
+ Selector: bw.selector,
+ WriteConcern: bw.writeConcern,
+ Crypt: bw.client.cryptFLE,
+ ServerAPI: bw.client.serverAPI,
+ Timeout: bw.client.timeout,
+ Logger: bw.client.logger,
+ Authenticator: bw.client.authenticator,
+ Name: driverutil.BulkWriteOp,
+ SendAfterClusterTime: true,
+ }.Execute(ctx)
+ var exception *ClientBulkWriteException
+
+ var ce CommandError
+ if errors.As(err, &ce) {
+ exception = &ClientBulkWriteException{
+ WriteError: &WriteError{
+ Code: int(ce.Code),
+ Message: ce.Message,
+ Raw: ce.Raw,
+ },
+ }
+ }
+ if len(batches.writeConcernErrors) > 0 || len(batches.writeErrors) > 0 {
+ if exception == nil {
+ exception = new(ClientBulkWriteException)
+ }
+ exception.WriteConcernErrors = batches.writeConcernErrors
+ exception.WriteErrors = batches.writeErrors
+ }
+ if exception != nil {
+ var hasSuccess bool
+ if batches.ordered {
+ _, ok := batches.writeErrors[0]
+ hasSuccess = !ok
+ } else {
+ hasSuccess = len(batches.writeErrors) < len(bw.writePairs)
+ }
+ if hasSuccess {
+ exception.PartialResult = batches.result
+ }
+ return *exception
+ }
+ return err
+}
+
+func (bw *clientBulkWrite) newCommand() func([]byte, description.SelectedServer) ([]byte, error) {
+ return func(dst []byte, desc description.SelectedServer) ([]byte, error) {
+ dst = bsoncore.AppendInt32Element(dst, "bulkWrite", 1)
+
+ dst = bsoncore.AppendBooleanElement(dst, "errorsOnly", bw.errorsOnly)
+ if bw.bypassDocumentValidation != nil && (desc.WireVersion != nil && driverutil.VersionRangeIncludes(*desc.WireVersion, 4)) {
+ dst = bsoncore.AppendBooleanElement(dst, "bypassDocumentValidation", *bw.bypassDocumentValidation)
+ }
+ if bw.comment != nil {
+ comment, err := marshalValue(bw.comment, bw.client.bsonOpts, bw.client.registry)
+ if err != nil {
+ return nil, err
+ }
+ dst = bsoncore.AppendValueElement(dst, "comment", comment)
+ }
+ dst = bsoncore.AppendBooleanElement(dst, "ordered", bw.ordered == nil || *bw.ordered)
+ if bw.let != nil {
+ let, err := marshal(bw.let, bw.client.bsonOpts, bw.client.registry)
+ if err != nil {
+ return nil, err
+ }
+ dst = bsoncore.AppendDocumentElement(dst, "let", let)
+ }
+ // Set rawData for 8.2+ servers.
+ if bw.rawData != nil && desc.WireVersion != nil && driverutil.VersionRangeIncludes(*desc.WireVersion, 27) {
+ dst = bsoncore.AppendBooleanElement(dst, "rawData", *bw.rawData)
+ }
+ if len(bw.additionalCmd) > 0 {
+ doc, err := bson.Marshal(bw.additionalCmd)
+ if err != nil {
+ return nil, err
+ }
+ dst = append(dst, doc[4:len(doc)-1]...)
+ }
+ return dst, nil
+ }
+}
+
+type cursorInfo struct {
+ Ok bool
+ Idx int32
+ Code *int32
+ Errmsg *string
+ ErrInfo bson.Raw
+ N int32
+ NModified *int32
+ Upserted *struct {
+ ID any `bson:"_id"`
+ }
+}
+
+func (cur *cursorInfo) extractError() *WriteError {
+ if cur.Ok {
+ return nil
+ }
+ err := &WriteError{
+ Index: int(cur.Idx),
+ Details: cur.ErrInfo,
+ }
+ if cur.Code != nil {
+ err.Code = int(*cur.Code)
+ }
+ if cur.Errmsg != nil {
+ err.Message = *cur.Errmsg
+ }
+ return err
+}
+
+type modelBatches struct {
+ session *session.Client
+ client *Client
+
+ ordered bool
+ writePairs []clientBulkWritePair
+
+ offset int
+
+ retryMode driver.RetryMode // RetryNone by default
+ cursorHandlers []func(*cursorInfo, bson.Raw) bool
+ newIDMap map[int]any
+
+ result *ClientBulkWriteResult
+ writeConcernErrors []WriteConcernError
+ writeErrors map[int]WriteError
+}
+
+var _ driver.OperationBatches = &modelBatches{}
+
+func (mb *modelBatches) IsOrdered() *bool {
+ return &mb.ordered
+}
+
+func (mb *modelBatches) AdvanceBatches(n int) {
+ mb.offset += n
+ if mb.offset > len(mb.writePairs) {
+ mb.offset = len(mb.writePairs)
+ }
+}
+
+func (mb *modelBatches) Size() int {
+ if mb.offset > len(mb.writePairs) {
+ return 0
+ }
+ return len(mb.writePairs) - mb.offset
+}
+
+func (mb *modelBatches) AppendBatchSequence(dst []byte, maxCount, totalSize int) (int, []byte, error) {
+ fn := functionSet{
+ appendStart: func(dst []byte, identifier string) (int32, []byte) {
+ var idx int32
+ dst = wiremessage.AppendMsgSectionType(dst, wiremessage.DocumentSequence)
+ idx, dst = bsoncore.ReserveLength(dst)
+ dst = append(dst, identifier...)
+ dst = append(dst, 0x00)
+ return idx, dst
+ },
+ appendDocument: func(dst []byte, _ string, doc []byte) []byte {
+ dst = append(dst, doc...)
+ return dst
+ },
+ updateLength: func(dst []byte, idx, length int32) []byte {
+ dst = bsoncore.UpdateLength(dst, idx, length)
+ return dst
+ },
+ }
+ return mb.appendBatches(fn, dst, maxCount, totalSize)
+}
+
+func (mb *modelBatches) AppendBatchArray(dst []byte, maxCount, totalSize int) (int, []byte, error) {
+ fn := functionSet{
+ appendStart: bsoncore.AppendArrayElementStart,
+ appendDocument: bsoncore.AppendDocumentElement,
+ updateLength: func(dst []byte, idx, _ int32) []byte {
+ dst, _ = bsoncore.AppendArrayEnd(dst, idx)
+ return dst
+ },
+ }
+ return mb.appendBatches(fn, dst, maxCount, totalSize)
+}
+
+type functionSet struct {
+ appendStart func([]byte, string) (int32, []byte)
+ appendDocument func([]byte, string, []byte) []byte
+ updateLength func([]byte, int32, int32) []byte
+}
+
+func (mb *modelBatches) appendBatches(fn functionSet, dst []byte, maxCount, totalSize int) (int, []byte, error) {
+ if mb.Size() == 0 {
+ return 0, dst, io.EOF
+ }
+
+ mb.cursorHandlers = mb.cursorHandlers[:0]
+ mb.newIDMap = make(map[int]any)
+
+ nsMap := make(map[string]int)
+ getNsIndex := func(namespace string) (int, bool) {
+ v, ok := nsMap[namespace]
+ if ok {
+ return v, ok
+ }
+ nsIdx := len(nsMap)
+ nsMap[namespace] = nsIdx
+ return nsIdx, ok
+ }
+
+ canRetry := true
+ l := len(dst)
+
+ opsIdx, dst := fn.appendStart(dst, "ops")
+ nsIdx, nsDst := fn.appendStart(nil, "nsInfo")
+
+ totalSize -= 1000
+ size := len(dst) + len(nsDst)
+ var n int
+ for i := mb.offset; i < len(mb.writePairs); i++ {
+ if n == maxCount {
+ break
+ }
+
+ ns := mb.writePairs[i].namespace
+ nsIdx, exists := getNsIndex(ns)
+
+ var doc bsoncore.Document
+ var err error
+ switch model := mb.writePairs[i].model.(type) {
+ case *ClientInsertOneModel:
+ mb.cursorHandlers = append(mb.cursorHandlers, mb.appendInsertResult)
+ var id any
+ id, doc, err = (&clientInsertDoc{
+ namespace: nsIdx,
+ document: model.Document,
+ }).marshal(mb.client.bsonOpts, mb.client.registry)
+ if err != nil {
+ break
+ }
+ mb.newIDMap[i] = id
+ case *ClientUpdateOneModel:
+ mb.cursorHandlers = append(mb.cursorHandlers, mb.appendUpdateResult)
+ doc, err = (&clientUpdateDoc{
+ namespace: nsIdx,
+ filter: model.Filter,
+ update: model.Update,
+ hint: model.Hint,
+ arrayFilters: model.ArrayFilters,
+ collation: model.Collation,
+ upsert: model.Upsert,
+ sort: model.Sort,
+ multi: false,
+ checkDollarKey: true,
+ }).marshal(mb.client.bsonOpts, mb.client.registry)
+ case *ClientUpdateManyModel:
+ canRetry = false
+ mb.cursorHandlers = append(mb.cursorHandlers, mb.appendUpdateResult)
+ doc, err = (&clientUpdateDoc{
+ namespace: nsIdx,
+ filter: model.Filter,
+ update: model.Update,
+ hint: model.Hint,
+ arrayFilters: model.ArrayFilters,
+ collation: model.Collation,
+ upsert: model.Upsert,
+ multi: true,
+ checkDollarKey: true,
+ }).marshal(mb.client.bsonOpts, mb.client.registry)
+ case *ClientReplaceOneModel:
+ mb.cursorHandlers = append(mb.cursorHandlers, mb.appendUpdateResult)
+ doc, err = (&clientUpdateDoc{
+ namespace: nsIdx,
+ filter: model.Filter,
+ update: model.Replacement,
+ hint: model.Hint,
+ arrayFilters: nil,
+ collation: model.Collation,
+ upsert: model.Upsert,
+ sort: model.Sort,
+ multi: false,
+ checkDollarKey: false,
+ }).marshal(mb.client.bsonOpts, mb.client.registry)
+ case *ClientDeleteOneModel:
+ mb.cursorHandlers = append(mb.cursorHandlers, mb.appendDeleteResult)
+ doc, err = (&clientDeleteDoc{
+ namespace: nsIdx,
+ filter: model.Filter,
+ collation: model.Collation,
+ hint: model.Hint,
+ multi: false,
+ }).marshal(mb.client.bsonOpts, mb.client.registry)
+ case *ClientDeleteManyModel:
+ canRetry = false
+ mb.cursorHandlers = append(mb.cursorHandlers, mb.appendDeleteResult)
+ doc, err = (&clientDeleteDoc{
+ namespace: nsIdx,
+ filter: model.Filter,
+ collation: model.Collation,
+ hint: model.Hint,
+ multi: true,
+ }).marshal(mb.client.bsonOpts, mb.client.registry)
+ default:
+ mb.cursorHandlers = append(mb.cursorHandlers, nil)
+ }
+ if err != nil {
+ return 0, nil, err
+ }
+ length := len(doc)
+ if !exists {
+ length += len(ns)
+ }
+ size += length
+ if size >= totalSize {
+ break
+ }
+
+ dst = fn.appendDocument(dst, strconv.Itoa(n), doc)
+ if !exists {
+ idx, doc := bsoncore.AppendDocumentStart(nil)
+ doc = bsoncore.AppendStringElement(doc, "ns", ns)
+ doc, _ = bsoncore.AppendDocumentEnd(doc, idx)
+ nsDst = fn.appendDocument(nsDst, strconv.Itoa(n), doc)
+ }
+ n++
+ }
+ if n == 0 {
+ return 0, dst[:l], nil
+ }
+
+ dst = fn.updateLength(dst, opsIdx, int32(len(dst[opsIdx:])))
+ nsDst = fn.updateLength(nsDst, nsIdx, int32(len(nsDst[nsIdx:])))
+ dst = append(dst, nsDst...)
+
+ mb.retryMode = driver.RetryNone
+ if mb.client.retryWrites && canRetry {
+ mb.retryMode = driver.RetryOnce
+ }
+ return n, dst, nil
+}
+
+func (mb *modelBatches) processResponse(ctx context.Context, resp bsoncore.Document, info driver.ResponseInfo) error {
+ var writeCmdErr driver.WriteCommandError
+ if errors.As(info.Error, &writeCmdErr) && writeCmdErr.WriteConcernError != nil {
+ wce := convertDriverWriteConcernError(writeCmdErr.WriteConcernError)
+ if wce != nil {
+ mb.writeConcernErrors = append(mb.writeConcernErrors, *wce)
+ }
+ }
+ if len(resp) == 0 {
+ return nil
+ }
+ var res struct {
+ Ok bool
+ Cursor bsoncore.Document
+ NDeleted int32
+ NInserted int32
+ NMatched int32
+ NModified int32
+ NUpserted int32
+ NErrors int32
+ Code int32
+ Errmsg string
+ }
+ err := bson.Unmarshal(resp, &res)
+ if err != nil {
+ return err
+ }
+ if !res.Ok {
+ return ClientBulkWriteException{
+ WriteError: &WriteError{
+ Code: int(res.Code),
+ Message: res.Errmsg,
+ Raw: bson.Raw(resp),
+ },
+ WriteConcernErrors: mb.writeConcernErrors,
+ WriteErrors: mb.writeErrors,
+ PartialResult: mb.result,
+ }
+ }
+
+ if mb.result.Acknowledged {
+ mb.result.DeletedCount += int64(res.NDeleted)
+ mb.result.InsertedCount += int64(res.NInserted)
+ mb.result.MatchedCount += int64(res.NMatched)
+ mb.result.ModifiedCount += int64(res.NModified)
+ mb.result.UpsertedCount += int64(res.NUpserted)
+ }
+
+ var cursorRes driver.CursorResponse
+ cursorRes, err = driver.NewCursorResponse(res.Cursor, info)
+ if err != nil {
+ return err
+ }
+ var bCursor *driver.BatchCursor
+ bCursor, err = driver.NewBatchCursor(cursorRes, mb.session, mb.client.clock,
+ driver.CursorOptions{
+ CommandMonitor: mb.client.monitor,
+ Crypt: mb.client.cryptFLE,
+ ServerAPI: mb.client.serverAPI,
+ MarshalValueEncoderFn: newEncoderFn(mb.client.bsonOpts, mb.client.registry),
+ },
+ )
+ if err != nil {
+ return err
+ }
+ var cursor *Cursor
+ cursor, err = newCursor(bCursor, mb.client.bsonOpts, mb.client.registry,
+
+ // This op doesn't return a cursor to the user, so setting the client
+ // timeout should be a no-op.
+ withCursorOptionClientTimeout(mb.client.timeout))
+ if err != nil {
+ return err
+ }
+ defer cursor.Close(ctx)
+
+ ok := true
+ for cursor.Next(ctx) {
+ var cur cursorInfo
+ err = cursor.Decode(&cur)
+ if err != nil {
+ return err
+ }
+ if int(cur.Idx) >= len(mb.cursorHandlers) {
+ continue
+ }
+ ok = mb.cursorHandlers[int(cur.Idx)](&cur, cursor.Current) && ok
+ }
+ err = cursor.Err()
+ if err != nil {
+ return err
+ }
+ if mb.ordered && (writeCmdErr.WriteConcernError != nil || !ok || !res.Ok || res.NErrors > 0) {
+ return ClientBulkWriteException{
+ WriteConcernErrors: mb.writeConcernErrors,
+ WriteErrors: mb.writeErrors,
+ PartialResult: mb.result,
+ }
+ }
+ return nil
+}
+
+func (mb *modelBatches) appendDeleteResult(cur *cursorInfo, raw bson.Raw) bool {
+ idx := int(cur.Idx) + mb.offset
+ if err := cur.extractError(); err != nil {
+ err.Raw = raw
+ if mb.writeErrors == nil {
+ mb.writeErrors = make(map[int]WriteError)
+ }
+ mb.writeErrors[idx] = *err
+ return false
+ }
+
+ if mb.result.Acknowledged {
+ if mb.result.DeleteResults == nil {
+ mb.result.DeleteResults = make(map[int]ClientBulkWriteDeleteResult)
+ }
+ mb.result.DeleteResults[idx] = ClientBulkWriteDeleteResult{int64(cur.N)}
+ }
+
+ return true
+}
+
+func (mb *modelBatches) appendInsertResult(cur *cursorInfo, raw bson.Raw) bool {
+ idx := int(cur.Idx) + mb.offset
+ if err := cur.extractError(); err != nil {
+ err.Raw = raw
+ if mb.writeErrors == nil {
+ mb.writeErrors = make(map[int]WriteError)
+ }
+ mb.writeErrors[idx] = *err
+ return false
+ }
+
+ if mb.result.Acknowledged {
+ if mb.result.InsertResults == nil {
+ mb.result.InsertResults = make(map[int]ClientBulkWriteInsertResult)
+ }
+ mb.result.InsertResults[idx] = ClientBulkWriteInsertResult{mb.newIDMap[idx]}
+ }
+
+ return true
+}
+
+func (mb *modelBatches) appendUpdateResult(cur *cursorInfo, raw bson.Raw) bool {
+ idx := int(cur.Idx) + mb.offset
+ if err := cur.extractError(); err != nil {
+ err.Raw = raw
+ if mb.writeErrors == nil {
+ mb.writeErrors = make(map[int]WriteError)
+ }
+ mb.writeErrors[idx] = *err
+ return false
+ }
+
+ if mb.result.Acknowledged {
+ if mb.result.UpdateResults == nil {
+ mb.result.UpdateResults = make(map[int]ClientBulkWriteUpdateResult)
+ }
+ result := ClientBulkWriteUpdateResult{
+ MatchedCount: int64(cur.N),
+ }
+ if cur.NModified != nil {
+ result.ModifiedCount = int64(*cur.NModified)
+ }
+ if cur.Upserted != nil {
+ result.UpsertedID = cur.Upserted.ID
+ }
+ mb.result.UpdateResults[idx] = result
+ }
+
+ return true
+}
+
+type clientInsertDoc struct {
+ namespace int
+ document any
+}
+
+func (d *clientInsertDoc) marshal(bsonOpts *options.BSONOptions, registry *bson.Registry) (any, bsoncore.Document, error) {
+ uidx, doc := bsoncore.AppendDocumentStart(nil)
+
+ doc = bsoncore.AppendInt32Element(doc, "insert", int32(d.namespace))
+ f, err := marshal(d.document, bsonOpts, registry)
+ if err != nil {
+ return nil, nil, err
+ }
+ var id any
+ f, id, err = ensureID(f, bson.NilObjectID, bsonOpts, registry)
+ if err != nil {
+ return nil, nil, err
+ }
+ doc = bsoncore.AppendDocumentElement(doc, "document", f)
+ doc, err = bsoncore.AppendDocumentEnd(doc, uidx)
+ return id, doc, err
+}
+
+type clientUpdateDoc struct {
+ namespace int
+ filter any
+ update any
+ hint any
+ arrayFilters []any
+ collation *options.Collation
+ sort any
+ upsert *bool
+ multi bool
+ checkDollarKey bool
+}
+
+func (d *clientUpdateDoc) marshal(bsonOpts *options.BSONOptions, registry *bson.Registry) (bsoncore.Document, error) {
+ uidx, doc := bsoncore.AppendDocumentStart(nil)
+
+ doc = bsoncore.AppendInt32Element(doc, "update", int32(d.namespace))
+
+ if d.filter == nil {
+ return nil, fmt.Errorf("update filter cannot be nil")
+ }
+ f, err := marshal(d.filter, bsonOpts, registry)
+ if err != nil {
+ return nil, err
+ }
+ doc = bsoncore.AppendDocumentElement(doc, "filter", f)
+
+ u, err := marshalUpdateValue(d.update, bsonOpts, registry, d.checkDollarKey)
+ if err != nil {
+ return nil, err
+ }
+ doc = bsoncore.AppendValueElement(doc, "updateMods", u)
+ doc = bsoncore.AppendBooleanElement(doc, "multi", d.multi)
+
+ if d.arrayFilters != nil {
+ reg := registry
+ arr, err := marshalValue(d.arrayFilters, bsonOpts, reg)
+ if err != nil {
+ return nil, err
+ }
+ doc = bsoncore.AppendArrayElement(doc, "arrayFilters", arr.Data)
+ }
+
+ if d.collation != nil {
+ doc = bsoncore.AppendDocumentElement(doc, "collation", toDocument(d.collation))
+ }
+
+ if d.upsert != nil {
+ doc = bsoncore.AppendBooleanElement(doc, "upsert", *d.upsert)
+ }
+
+ if d.hint != nil {
+ if isUnorderedMap(d.hint) {
+ return nil, ErrMapForOrderedArgument{"hint"}
+ }
+ hintVal, err := marshalValue(d.hint, bsonOpts, registry)
+ if err != nil {
+ return nil, err
+ }
+ doc = bsoncore.AppendValueElement(doc, "hint", hintVal)
+ }
+
+ if d.sort != nil {
+ if isUnorderedMap(d.sort) {
+ return nil, ErrMapForOrderedArgument{"sort"}
+ }
+ sortVal, err := marshalValue(d.sort, bsonOpts, registry)
+ if err != nil {
+ return nil, err
+ }
+ doc = bsoncore.AppendValueElement(doc, "sort", sortVal)
+ }
+
+ return bsoncore.AppendDocumentEnd(doc, uidx)
+}
+
+type clientDeleteDoc struct {
+ namespace int
+ filter any
+ collation *options.Collation
+ hint any
+ multi bool
+}
+
+func (d *clientDeleteDoc) marshal(bsonOpts *options.BSONOptions, registry *bson.Registry) (bsoncore.Document, error) {
+ didx, doc := bsoncore.AppendDocumentStart(nil)
+
+ doc = bsoncore.AppendInt32Element(doc, "delete", int32(d.namespace))
+
+ if d.filter == nil {
+ return nil, fmt.Errorf("delete filter cannot be nil")
+ }
+ f, err := marshal(d.filter, bsonOpts, registry)
+ if err != nil {
+ return nil, err
+ }
+ doc = bsoncore.AppendDocumentElement(doc, "filter", f)
+ doc = bsoncore.AppendBooleanElement(doc, "multi", d.multi)
+
+ if d.collation != nil {
+ doc = bsoncore.AppendDocumentElement(doc, "collation", toDocument(d.collation))
+ }
+ if d.hint != nil {
+ if isUnorderedMap(d.hint) {
+ return nil, ErrMapForOrderedArgument{"hint"}
+ }
+ hintVal, err := marshalValue(d.hint, bsonOpts, registry)
+ if err != nil {
+ return nil, err
+ }
+ doc = bsoncore.AppendValueElement(doc, "hint", hintVal)
+ }
+ return bsoncore.AppendDocumentEnd(doc, didx)
+}
diff --git a/vendor/go.mongodb.org/mongo-driver/v2/mongo/client_bulk_write_models.go b/vendor/go.mongodb.org/mongo-driver/v2/mongo/client_bulk_write_models.go
new file mode 100644
index 0000000..da0ea18
--- /dev/null
+++ b/vendor/go.mongodb.org/mongo-driver/v2/mongo/client_bulk_write_models.go
@@ -0,0 +1,318 @@
+// Copyright (C) MongoDB, Inc. 2024-present.
+//
+// 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
+
+package mongo
+
+import (
+ "go.mongodb.org/mongo-driver/v2/mongo/options"
+)
+
+// ClientWriteModel is an interface implemented by models that can be used in a client-level BulkWrite operation. Each
+// ClientWriteModel represents a write.
+//
+// This interface is implemented by ClientDeleteOneModel, ClientDeleteManyModel, ClientInsertOneModel,
+// ClientReplaceOneModel, ClientUpdateOneModel, and ClientUpdateManyModel. Custom implementations of this interface must
+// not be used.
+type ClientWriteModel interface {
+ clientWriteModel()
+}
+
+// ClientInsertOneModel is used to insert a single document in a client-level BulkWrite operation.
+//
+// See corresponding setter methods for documentation.
+type ClientInsertOneModel struct {
+ Document any
+}
+
+// NewClientInsertOneModel creates a new ClientInsertOneModel.
+func NewClientInsertOneModel() *ClientInsertOneModel {
+ return &ClientInsertOneModel{}
+}
+
+func (*ClientInsertOneModel) clientWriteModel() {}
+
+// SetDocument specifies the document to be inserted. The document cannot be nil. If it does not have an _id field when
+// transformed into BSON, one will be added automatically to the marshalled document. The original document will not be
+// modified.
+func (iom *ClientInsertOneModel) SetDocument(doc any) *ClientInsertOneModel {
+ iom.Document = doc
+ return iom
+}
+
+// ClientUpdateOneModel is used to update at most one document in a client-level BulkWrite operation.
+//
+// See corresponding setter methods for documentation.
+type ClientUpdateOneModel struct {
+ Collation *options.Collation
+ Upsert *bool
+ Filter any
+ Update any
+ ArrayFilters []any
+ Hint any
+ Sort any
+}
+
+// NewClientUpdateOneModel creates a new ClientUpdateOneModel.
+func NewClientUpdateOneModel() *ClientUpdateOneModel {
+ return &ClientUpdateOneModel{}
+}
+
+func (*ClientUpdateOneModel) clientWriteModel() {}
+
+// SetHint specifies the index to use for the operation. This should either be the index name as a string or the index
+// specification as a document. The default value is nil, which means that no hint will be sent.
+func (uom *ClientUpdateOneModel) SetHint(hint any) *ClientUpdateOneModel {
+ uom.Hint = hint
+ return uom
+}
+
+// SetFilter specifies a filter to use to select the document to update. The filter must be a document containing query
+// operators. It cannot be nil. If the filter matches multiple documents, one will be selected from the matching
+// documents.
+func (uom *ClientUpdateOneModel) SetFilter(filter any) *ClientUpdateOneModel {
+ uom.Filter = filter
+ return uom
+}
+
+// SetUpdate specifies the modifications to be made to the selected document. The value must be a document containing
+// update operators (https://www.mongodb.com/docs/manual/reference/operator/update/). It cannot be nil or empty.
+func (uom *ClientUpdateOneModel) SetUpdate(update any) *ClientUpdateOneModel {
+ uom.Update = update
+ return uom
+}
+
+// SetArrayFilters specifies a set of filters to determine which elements should be modified when updating an array
+// field.
+func (uom *ClientUpdateOneModel) SetArrayFilters(filters []any) *ClientUpdateOneModel {
+ uom.ArrayFilters = filters
+ return uom
+}
+
+// SetCollation specifies a collation to use for string comparisons. The default is nil, meaning no collation will be
+// used.
+func (uom *ClientUpdateOneModel) SetCollation(collation *options.Collation) *ClientUpdateOneModel {
+ uom.Collation = collation
+ return uom
+}
+
+// SetUpsert specifies whether or not a new document should be inserted if no document matching the filter is found. If
+// an upsert is performed, the _id of the upserted document can be retrieved from the UpdateResults field of the
+// ClientBulkWriteResult.
+func (uom *ClientUpdateOneModel) SetUpsert(upsert bool) *ClientUpdateOneModel {
+ uom.Upsert = &upsert
+ return uom
+}
+
+// SetSort specifies which document the operation updates if the query matches multiple documents. The first document
+// matched by the sort order will be updated. This option is only valid for MongoDB versions >= 8.0. The sort parameter
+// is evaluated sequentially, so the driver will return an error if it is a multi-key map (which is unordeded). The
+// default value is nil.
+func (uom *ClientUpdateOneModel) SetSort(sort any) *ClientUpdateOneModel {
+ uom.Sort = sort
+ return uom
+}
+
+// ClientUpdateManyModel is used to update multiple documents in a client-level BulkWrite operation.
+//
+// See corresponding setter methods for documentation.
+type ClientUpdateManyModel struct {
+ Collation *options.Collation
+ Upsert *bool
+ Filter any
+ Update any
+ ArrayFilters []any
+ Hint any
+}
+
+// NewClientUpdateManyModel creates a new ClientUpdateManyModel.
+func NewClientUpdateManyModel() *ClientUpdateManyModel {
+ return &ClientUpdateManyModel{}
+}
+
+func (*ClientUpdateManyModel) clientWriteModel() {}
+
+// SetHint specifies the index to use for the operation. This should either be the index name as a string or the index
+// specification as a document. The default value is nil, which means that no hint will be sent.
+func (umm *ClientUpdateManyModel) SetHint(hint any) *ClientUpdateManyModel {
+ umm.Hint = hint
+ return umm
+}
+
+// SetFilter specifies a filter to use to select documents to update. The filter must be a document containing query
+// operators. It cannot be nil.
+func (umm *ClientUpdateManyModel) SetFilter(filter any) *ClientUpdateManyModel {
+ umm.Filter = filter
+ return umm
+}
+
+// SetUpdate specifies the modifications to be made to the selected documents. The value must be a document containing
+// update operators (https://www.mongodb.com/docs/manual/reference/operator/update/). It cannot be nil or empty.
+func (umm *ClientUpdateManyModel) SetUpdate(update any) *ClientUpdateManyModel {
+ umm.Update = update
+ return umm
+}
+
+// SetArrayFilters specifies a set of filters to determine which elements should be modified when updating an array
+// field.
+func (umm *ClientUpdateManyModel) SetArrayFilters(filters []any) *ClientUpdateManyModel {
+ umm.ArrayFilters = filters
+ return umm
+}
+
+// SetCollation specifies a collation to use for string comparisons. The default is nil, meaning no collation will be
+// used.
+func (umm *ClientUpdateManyModel) SetCollation(collation *options.Collation) *ClientUpdateManyModel {
+ umm.Collation = collation
+ return umm
+}
+
+// SetUpsert specifies whether or not a new document should be inserted if no document matching the filter is found. If
+// an upsert is performed, the _id of the upserted document can be retrieved from the UpdateResults field of the
+// ClientBulkWriteResult.
+func (umm *ClientUpdateManyModel) SetUpsert(upsert bool) *ClientUpdateManyModel {
+ umm.Upsert = &upsert
+ return umm
+}
+
+// ClientReplaceOneModel is used to replace at most one document in a client-level BulkWrite operation.
+//
+// See corresponding setter methods for documentation.
+type ClientReplaceOneModel struct {
+ Collation *options.Collation
+ Upsert *bool
+ Filter any
+ Replacement any
+ Hint any
+ Sort any
+}
+
+// NewClientReplaceOneModel creates a new ClientReplaceOneModel.
+func NewClientReplaceOneModel() *ClientReplaceOneModel {
+ return &ClientReplaceOneModel{}
+}
+
+func (*ClientReplaceOneModel) clientWriteModel() {}
+
+// SetHint specifies the index to use for the operation. This should either be the index name as a string or the index
+// specification as a document. The default value is nil, which means that no hint will be sent.
+func (rom *ClientReplaceOneModel) SetHint(hint any) *ClientReplaceOneModel {
+ rom.Hint = hint
+ return rom
+}
+
+// SetFilter specifies a filter to use to select the document to replace. The filter must be a document containing query
+// operators. It cannot be nil. If the filter matches multiple documents, one will be selected from the matching
+// documents.
+func (rom *ClientReplaceOneModel) SetFilter(filter any) *ClientReplaceOneModel {
+ rom.Filter = filter
+ return rom
+}
+
+// SetReplacement specifies a document that will be used to replace the selected document. It cannot be nil and cannot
+// contain any update operators (https://www.mongodb.com/docs/manual/reference/operator/update/).
+func (rom *ClientReplaceOneModel) SetReplacement(rep any) *ClientReplaceOneModel {
+ rom.Replacement = rep
+ return rom
+}
+
+// SetCollation specifies a collation to use for string comparisons. The default is nil, meaning no collation will be
+// used.
+func (rom *ClientReplaceOneModel) SetCollation(collation *options.Collation) *ClientReplaceOneModel {
+ rom.Collation = collation
+ return rom
+}
+
+// SetUpsert specifies whether or not the replacement document should be inserted if no document matching the filter is
+// found. If an upsert is performed, the _id of the upserted document can be retrieved from the UpdateResults field of the
+// BulkWriteResult.
+func (rom *ClientReplaceOneModel) SetUpsert(upsert bool) *ClientReplaceOneModel {
+ rom.Upsert = &upsert
+ return rom
+}
+
+// SetSort specifies which document the operation replaces if the query matches multiple documents. The first document
+// matched by the sort order will be replaced. This option is only valid for MongoDB versions >= 8.0. The sort parameter
+// is evaluated sequentially, so the driver will return an error if it is a multi-key map (which is unordeded). The
+// default value is nil.
+func (rom *ClientReplaceOneModel) SetSort(sort any) *ClientReplaceOneModel {
+ rom.Sort = sort
+ return rom
+}
+
+// ClientDeleteOneModel is used to delete at most one document in a client-level BulkWriteOperation.
+//
+// See corresponding setter methods for documentation.
+type ClientDeleteOneModel struct {
+ Filter any
+ Collation *options.Collation
+ Hint any
+}
+
+// NewClientDeleteOneModel creates a new ClientDeleteOneModel.
+func NewClientDeleteOneModel() *ClientDeleteOneModel {
+ return &ClientDeleteOneModel{}
+}
+
+func (*ClientDeleteOneModel) clientWriteModel() {}
+
+// SetFilter specifies a filter to use to select the document to delete. The filter must be a document containing query
+// operators. It cannot be nil. If the filter matches multiple documents, one will be selected from the matching
+// documents.
+func (dom *ClientDeleteOneModel) SetFilter(filter any) *ClientDeleteOneModel {
+ dom.Filter = filter
+ return dom
+}
+
+// SetCollation specifies a collation to use for string comparisons. The default is nil, meaning no collation will be
+// used.
+func (dom *ClientDeleteOneModel) SetCollation(collation *options.Collation) *ClientDeleteOneModel {
+ dom.Collation = collation
+ return dom
+}
+
+// SetHint specifies the index to use for the operation. This should either be the index name as a string or the index
+// specification as a document. The default value is nil, which means that no hint will be sent.
+func (dom *ClientDeleteOneModel) SetHint(hint any) *ClientDeleteOneModel {
+ dom.Hint = hint
+ return dom
+}
+
+// ClientDeleteManyModel is used to delete multiple documents in a client-level BulkWrite operation.
+//
+// See corresponding setter methods for documentation.
+type ClientDeleteManyModel struct {
+ Filter any
+ Collation *options.Collation
+ Hint any
+}
+
+// NewClientDeleteManyModel creates a new ClientDeleteManyModel.
+func NewClientDeleteManyModel() *ClientDeleteManyModel {
+ return &ClientDeleteManyModel{}
+}
+
+func (*ClientDeleteManyModel) clientWriteModel() {}
+
+// SetFilter specifies a filter to use to select documents to delete. The filter must be a document containing query
+// operators. It cannot be nil.
+func (dmm *ClientDeleteManyModel) SetFilter(filter any) *ClientDeleteManyModel {
+ dmm.Filter = filter
+ return dmm
+}
+
+// SetCollation specifies a collation to use for string comparisons. The default is nil, meaning no collation will be
+// used.
+func (dmm *ClientDeleteManyModel) SetCollation(collation *options.Collation) *ClientDeleteManyModel {
+ dmm.Collation = collation
+ return dmm
+}
+
+// SetHint specifies the index to use for the operation. This should either be the index name as a string or the index
+// specification as a document. The default value is nil, which means that no hint will be sent.
+func (dmm *ClientDeleteManyModel) SetHint(hint any) *ClientDeleteManyModel {
+ dmm.Hint = hint
+ return dmm
+}
diff --git a/vendor/go.mongodb.org/mongo-driver/v2/mongo/client_encryption.go b/vendor/go.mongodb.org/mongo-driver/v2/mongo/client_encryption.go
new file mode 100644
index 0000000..d5538f0
--- /dev/null
+++ b/vendor/go.mongodb.org/mongo-driver/v2/mongo/client_encryption.go
@@ -0,0 +1,554 @@
+// Copyright (C) MongoDB, Inc. 2017-present.
+//
+// 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
+
+package mongo
+
+import (
+ "bytes"
+ "context"
+ "errors"
+ "fmt"
+ "strings"
+
+ "go.mongodb.org/mongo-driver/v2/bson"
+ "go.mongodb.org/mongo-driver/v2/internal/mongoutil"
+ "go.mongodb.org/mongo-driver/v2/mongo/options"
+ "go.mongodb.org/mongo-driver/v2/x/bsonx/bsoncore"
+ "go.mongodb.org/mongo-driver/v2/x/mongo/driver"
+ "go.mongodb.org/mongo-driver/v2/x/mongo/driver/mongocrypt"
+ mcopts "go.mongodb.org/mongo-driver/v2/x/mongo/driver/mongocrypt/options"
+)
+
+// ClientEncryption is used to create data keys and explicitly encrypt and decrypt BSON values.
+type ClientEncryption struct {
+ crypt driver.Crypt
+ keyVaultClient *Client
+ keyVaultColl *Collection
+ closed bool
+}
+
+// NewClientEncryption creates a new ClientEncryption instance configured with the given options.
+func NewClientEncryption(keyVaultClient *Client, opts ...options.Lister[options.ClientEncryptionOptions]) (*ClientEncryption, error) {
+ if keyVaultClient == nil {
+ return nil, errors.New("keyVaultClient must not be nil")
+ }
+
+ ce := &ClientEncryption{
+ keyVaultClient: keyVaultClient,
+ }
+ cea, err := mongoutil.NewOptions(opts...)
+ if err != nil {
+ return nil, err
+ }
+
+ // create keyVaultColl
+ db, coll := splitNamespace(cea.KeyVaultNamespace)
+ ce.keyVaultColl = ce.keyVaultClient.Database(db).Collection(coll, keyVaultCollOpts)
+
+ kmsProviders, err := marshal(cea.KmsProviders, nil, nil)
+ if err != nil {
+ return nil, fmt.Errorf("error creating KMS providers map: %w", err)
+ }
+
+ mc, err := mongocrypt.NewMongoCrypt(&mcopts.MongoCryptOptions{
+ KmsProviders: kmsProviders,
+ // Explicitly disable loading the crypt_shared library for the Crypt used for
+ // ClientEncryption because it's only needed for AutoEncryption and we don't expect users to
+ // have the crypt_shared library installed if they're using ClientEncryption.
+ CryptSharedLibDisabled: true,
+ HTTPClient: cea.HTTPClient,
+ KeyExpiration: cea.KeyExpiration,
+ })
+ if err != nil {
+ return nil, err
+ }
+
+ // create Crypt
+ kr := keyRetriever{coll: ce.keyVaultColl}
+ cir := collInfoRetriever{client: ce.keyVaultClient}
+ ce.crypt = driver.NewCrypt(&driver.CryptOptions{
+ MongoCrypt: mc,
+ KeyFn: kr.cryptKeys,
+ CollInfoFn: cir.cryptCollInfo,
+ TLSConfig: cea.TLSConfig,
+ })
+
+ return ce, nil
+}
+
+// CreateEncryptedCollection creates a new collection for Queryable Encryption with the help of automatic generation of new encryption data keys for null keyIds.
+// It returns the created collection and the encrypted fields document used to create it.
+func (ce *ClientEncryption) CreateEncryptedCollection(ctx context.Context,
+ db *Database, coll string, createOpts options.Lister[options.CreateCollectionOptions],
+ kmsProvider string, masterKey any,
+) (*Collection, bson.M, error) {
+ if ce.closed {
+ return nil, nil, ErrClientDisconnected
+ }
+
+ if createOpts == nil {
+ return nil, nil, errors.New("nil CreateCollectionOptions")
+ }
+
+ createArgs, err := mongoutil.NewOptions[options.CreateCollectionOptions](createOpts)
+ if err != nil {
+ return nil, nil, fmt.Errorf("failed to construct options from builder: %w", err)
+ }
+
+ ef := createArgs.EncryptedFields
+ if ef == nil {
+ return nil, nil, errors.New("no EncryptedFields defined for the collection")
+ }
+
+ efBSON, err := marshal(ef, db.bsonOpts, db.registry)
+ if err != nil {
+ return nil, nil, err
+ }
+ r := bson.NewDocumentReader(bytes.NewReader(efBSON))
+ dec := bson.NewDecoder(r)
+ dec.DefaultDocumentM()
+ var m bson.M
+ err = dec.Decode(&m)
+ if err != nil {
+ return nil, nil, err
+ }
+
+ if v, ok := m["fields"]; ok {
+ if fields, ok := v.(bson.A); ok {
+ for _, field := range fields {
+ if f, ok := field.(bson.M); !ok {
+ continue
+ } else if v, ok := f["keyId"]; ok && v == nil {
+ dkOpts := options.DataKey()
+ if masterKey != nil {
+ dkOpts.SetMasterKey(masterKey)
+ }
+ keyid, err := ce.CreateDataKey(ctx, kmsProvider, dkOpts)
+ if err != nil {
+ createArgs.EncryptedFields = m
+ return nil, m, err
+ }
+ f["keyId"] = keyid
+ }
+ }
+ createArgs.EncryptedFields = m
+ }
+ }
+
+ updatedCreateOpts := mongoutil.NewOptionsLister(createArgs, nil)
+ err = db.CreateCollection(ctx, coll, updatedCreateOpts)
+ if err != nil {
+ return nil, m, err
+ }
+ return db.Collection(coll), m, nil
+}
+
+// AddKeyAltName adds a keyAltName to the keyAltNames array of the key document in the key vault collection with the
+// given UUID (BSON binary subtype 0x04). Returns the previous version of the key document.
+func (ce *ClientEncryption) AddKeyAltName(ctx context.Context, id bson.Binary, keyAltName string) *SingleResult {
+ if ce.closed {
+ return &SingleResult{err: ErrClientDisconnected}
+ }
+
+ filter := bsoncore.NewDocumentBuilder().AppendBinary("_id", id.Subtype, id.Data).Build()
+ keyAltNameDoc := bsoncore.NewDocumentBuilder().AppendString("keyAltNames", keyAltName).Build()
+ update := bsoncore.NewDocumentBuilder().AppendDocument("$addToSet", keyAltNameDoc).Build()
+ return ce.keyVaultColl.FindOneAndUpdate(ctx, filter, update)
+}
+
+// CreateDataKey creates a new key document and inserts into the key vault collection. Returns the _id of the created
+// document as a UUID (BSON binary subtype 0x04).
+func (ce *ClientEncryption) CreateDataKey(
+ ctx context.Context,
+ kmsProvider string,
+ opts ...options.Lister[options.DataKeyOptions],
+) (bson.Binary, error) {
+ if ce.closed {
+ return bson.Binary{}, ErrClientDisconnected
+ }
+
+ args, err := mongoutil.NewOptions[options.DataKeyOptions](opts...)
+ if err != nil {
+ return bson.Binary{}, fmt.Errorf("failed to construct options from builder: %w", err)
+ }
+
+ co := &mcopts.DataKeyOptions{
+ KeyAltNames: args.KeyAltNames,
+ KeyMaterial: args.KeyMaterial,
+ }
+ if args.MasterKey != nil {
+ keyDoc, err := marshal(
+ args.MasterKey,
+ ce.keyVaultClient.bsonOpts,
+ ce.keyVaultClient.registry)
+ if err != nil {
+ return bson.Binary{}, err
+ }
+ co.MasterKey = keyDoc
+ }
+
+ // create data key document
+ dataKeyDoc, err := ce.crypt.CreateDataKey(ctx, kmsProvider, co)
+ if err != nil {
+ return bson.Binary{}, err
+ }
+
+ // insert key into key vault
+ _, err = ce.keyVaultColl.InsertOne(ctx, dataKeyDoc)
+ if err != nil {
+ return bson.Binary{}, err
+ }
+
+ subtype, data := bson.Raw(dataKeyDoc).Lookup("_id").Binary()
+ return bson.Binary{Subtype: subtype, Data: data}, nil
+}
+
+// transformExplicitEncryptionOptions creates explicit encryption options to be passed to libmongocrypt.
+func transformExplicitEncryptionOptions(opts ...options.Lister[options.EncryptOptions]) (*mcopts.ExplicitEncryptionOptions, error) {
+ args, err := mongoutil.NewOptions[options.EncryptOptions](opts...)
+ if err != nil {
+ return nil, err
+ }
+
+ transformed := &mcopts.ExplicitEncryptionOptions{
+ KeyID: args.KeyID,
+ KeyAltName: args.KeyAltName,
+ Algorithm: args.Algorithm,
+ QueryType: args.QueryType,
+ ContentionFactor: args.ContentionFactor,
+ }
+
+ if args.RangeOptions != nil {
+ rangeArgs, err := mongoutil.NewOptions[options.RangeOptions](args.RangeOptions)
+ if err != nil {
+ return nil, err
+ }
+
+ var transformedRange mcopts.ExplicitRangeOptions
+ if rangeArgs.Min != nil {
+ transformedRange.Min = &bsoncore.Value{Type: bsoncore.Type(rangeArgs.Min.Type), Data: rangeArgs.Min.Value}
+ }
+ if rangeArgs.Max != nil {
+ transformedRange.Max = &bsoncore.Value{Type: bsoncore.Type(rangeArgs.Max.Type), Data: rangeArgs.Max.Value}
+ }
+ if rangeArgs.Precision != nil {
+ transformedRange.Precision = rangeArgs.Precision
+ }
+ if rangeArgs.Sparsity != nil {
+ transformedRange.Sparsity = rangeArgs.Sparsity
+ }
+ if rangeArgs.TrimFactor != nil {
+ transformedRange.TrimFactor = rangeArgs.TrimFactor
+ }
+ transformed.RangeOptions = &transformedRange
+ }
+ if args.TextOptions != nil {
+ textArgs, err := mongoutil.NewOptions[options.TextOptions](args.TextOptions)
+ if err != nil {
+ return nil, err
+ }
+
+ transformedText := mcopts.ExplicitTextOptions{
+ CaseSensitive: textArgs.CaseSensitive,
+ DiacriticSensitive: textArgs.DiacriticSensitive,
+ }
+ if textArgs.Substring != nil {
+ substringOpts := mcopts.SubstringOptions(*textArgs.Substring)
+ transformedText.Substring = &substringOpts
+ }
+ if textArgs.Prefix != nil {
+ prefixOpts := mcopts.PrefixOptions(*textArgs.Prefix)
+ transformedText.Prefix = &prefixOpts
+ }
+ if textArgs.Suffix != nil {
+ suffixOpts := mcopts.SuffixOptions(*textArgs.Suffix)
+ transformedText.Suffix = &suffixOpts
+ }
+ transformed.SetTextOptions(transformedText)
+ }
+ return transformed, nil
+}
+
+// Encrypt encrypts a BSON value with the given key and algorithm. Returns an encrypted value (BSON binary of subtype 6).
+func (ce *ClientEncryption) Encrypt(
+ ctx context.Context,
+ val bson.RawValue,
+ opts ...options.Lister[options.EncryptOptions],
+) (bson.Binary, error) {
+ if ce.closed {
+ return bson.Binary{}, ErrClientDisconnected
+ }
+
+ transformed, err := transformExplicitEncryptionOptions(opts...)
+ if err != nil {
+ return bson.Binary{}, err
+ }
+ subtype, data, err := ce.crypt.EncryptExplicit(ctx, bsoncore.Value{Type: bsoncore.Type(val.Type), Data: val.Value}, transformed)
+ if err != nil {
+ return bson.Binary{}, err
+ }
+ return bson.Binary{Subtype: subtype, Data: data}, nil
+}
+
+// EncryptExpression encrypts an expression to query a range index.
+// On success, `result` is populated with the resulting BSON document.
+// `expr` 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: [, ]}]
+// $gt may also be $gte. $lt may also be $lte.
+// Only supported for queryType "range"
+func (ce *ClientEncryption) EncryptExpression(ctx context.Context, expr any, result any, opts ...options.Lister[options.EncryptOptions]) error {
+ if ce.closed {
+ return ErrClientDisconnected
+ }
+
+ transformed, err := transformExplicitEncryptionOptions(opts...)
+ if err != nil {
+ return err
+ }
+
+ exprDoc, err := marshal(expr, nil, nil)
+ if err != nil {
+ return err
+ }
+
+ encryptedExprDoc, err := ce.crypt.EncryptExplicitExpression(ctx, exprDoc, transformed)
+ if err != nil {
+ return err
+ }
+ if raw, ok := result.(*bson.Raw); ok {
+ // Avoid the cost of Unmarshal.
+ *raw = bson.Raw(encryptedExprDoc)
+ return nil
+ }
+ err = bson.Unmarshal([]byte(encryptedExprDoc), result)
+ if err != nil {
+ return err
+ }
+ return nil
+}
+
+// Decrypt decrypts an encrypted value (BSON binary of subtype 6) and returns the original BSON value.
+func (ce *ClientEncryption) Decrypt(ctx context.Context, val bson.Binary) (bson.RawValue, error) {
+ if ce.closed {
+ return bson.RawValue{}, ErrClientDisconnected
+ }
+
+ decrypted, err := ce.crypt.DecryptExplicit(ctx, val.Subtype, val.Data)
+ if err != nil {
+ return bson.RawValue{}, err
+ }
+
+ return bson.RawValue{Type: bson.Type(decrypted.Type), Value: decrypted.Data}, nil
+}
+
+// Close cleans up any resources associated with the ClientEncryption instance. This includes disconnecting the
+// key-vault Client instance.
+func (ce *ClientEncryption) Close(ctx context.Context) error {
+ if ce.closed {
+ return ErrClientDisconnected
+ }
+
+ ce.crypt.Close()
+ err := ce.keyVaultClient.Disconnect(ctx)
+ if err == nil {
+ ce.closed = true
+ }
+ return err
+}
+
+// DeleteKey removes the key document with the given UUID (BSON binary subtype 0x04) from the key vault collection.
+// Returns the result of the internal deleteOne() operation on the key vault collection.
+func (ce *ClientEncryption) DeleteKey(ctx context.Context, id bson.Binary) (*DeleteResult, error) {
+ if ce.closed {
+ return nil, ErrClientDisconnected
+ }
+
+ filter := bsoncore.NewDocumentBuilder().AppendBinary("_id", id.Subtype, id.Data).Build()
+ return ce.keyVaultColl.DeleteOne(ctx, filter)
+}
+
+// GetKeyByAltName returns a key document in the key vault collection with the given keyAltName.
+func (ce *ClientEncryption) GetKeyByAltName(ctx context.Context, keyAltName string) *SingleResult {
+ if ce.closed {
+ return &SingleResult{err: ErrClientDisconnected}
+ }
+
+ filter := bsoncore.NewDocumentBuilder().AppendString("keyAltNames", keyAltName).Build()
+ return ce.keyVaultColl.FindOne(ctx, filter)
+}
+
+// GetKey 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.
+func (ce *ClientEncryption) GetKey(ctx context.Context, id bson.Binary) *SingleResult {
+ if ce.closed {
+ return &SingleResult{err: ErrClientDisconnected}
+ }
+
+ filter := bsoncore.NewDocumentBuilder().AppendBinary("_id", id.Subtype, id.Data).Build()
+ return ce.keyVaultColl.FindOne(ctx, filter)
+}
+
+// GetKeys finds all documents in the key vault collection. Returns the result of the internal find() operation on the
+// key vault collection.
+func (ce *ClientEncryption) GetKeys(ctx context.Context) (*Cursor, error) {
+ if ce.closed {
+ return nil, ErrClientDisconnected
+ }
+
+ return ce.keyVaultColl.Find(ctx, bson.D{})
+}
+
+// RemoveKeyAltName removes a keyAltName from the keyAltNames array of the key document in the key vault collection with
+// the given UUID (BSON binary subtype 0x04). Returns the previous version of the key document.
+func (ce *ClientEncryption) RemoveKeyAltName(ctx context.Context, id bson.Binary, keyAltName string) *SingleResult {
+ if ce.closed {
+ return &SingleResult{err: ErrClientDisconnected}
+ }
+
+ filter := bsoncore.NewDocumentBuilder().AppendBinary("_id", id.Subtype, id.Data).Build()
+ update := bson.A{bson.D{{"$set", bson.D{{"keyAltNames", bson.D{{"$cond", bson.A{bson.D{{
+ "$eq",
+ bson.A{"$keyAltNames", bson.A{keyAltName}},
+ }}, "$$REMOVE", bson.D{{
+ "$filter",
+ bson.D{{"input", "$keyAltNames"}, {"cond", bson.D{{"$ne", bson.A{"$$this", keyAltName}}}}},
+ }}}}}}}}}}
+ return ce.keyVaultColl.FindOneAndUpdate(ctx, filter, update)
+}
+
+// setRewrapManyDataKeyWriteModels will prepare the WriteModel slice for a bulk updating rewrapped documents.
+func setRewrapManyDataKeyWriteModels(rewrappedDocuments []bsoncore.Document, writeModels *[]WriteModel) error {
+ const idKey = "_id"
+ const keyMaterial = "keyMaterial"
+ const masterKey = "masterKey"
+
+ if writeModels == nil {
+ return fmt.Errorf("writeModels pointer not set for location referenced")
+ }
+
+ // Append a slice of WriteModel with the update document per each rewrappedDoc _id filter.
+ for _, rewrappedDocument := range rewrappedDocuments {
+ // Prepare the new master key for update.
+ masterKeyValue, err := rewrappedDocument.LookupErr(masterKey)
+ if err != nil {
+ return err
+ }
+ masterKeyDoc := masterKeyValue.Document()
+
+ // Prepare the new material key for update.
+ keyMaterialValue, err := rewrappedDocument.LookupErr(keyMaterial)
+ if err != nil {
+ return err
+ }
+ keyMaterialSubtype, keyMaterialData := keyMaterialValue.Binary()
+ keyMaterialBinary := bson.Binary{Subtype: keyMaterialSubtype, Data: keyMaterialData}
+
+ // Prepare the _id filter for documents to update.
+ id, err := rewrappedDocument.LookupErr(idKey)
+ if err != nil {
+ return err
+ }
+
+ idSubtype, idData, ok := id.BinaryOK()
+ if !ok {
+ return fmt.Errorf("expected to assert %q as binary, got type %s", idKey, id.Type)
+ }
+ binaryID := bson.Binary{Subtype: idSubtype, Data: idData}
+
+ // Append the mutable document to the slice for bulk update.
+ *writeModels = append(*writeModels, NewUpdateOneModel().
+ SetFilter(bson.D{{idKey, binaryID}}).
+ SetUpdate(
+ bson.D{
+ {"$set", bson.D{{keyMaterial, keyMaterialBinary}, {masterKey, masterKeyDoc}}},
+ {"$currentDate", bson.D{{"updateDate", true}}},
+ },
+ ))
+ }
+ return nil
+}
+
+// RewrapManyDataKey decrypts and encrypts all matching data keys with a possibly new masterKey value. For all
+// matching documents, this method will overwrite the "masterKey", "updateDate", and "keyMaterial". On error, some
+// matching data keys may have been rewrapped.
+// libmongocrypt 1.5.2 is required. An error is returned if the detected version of libmongocrypt is less than 1.5.2.
+func (ce *ClientEncryption) RewrapManyDataKey(
+ ctx context.Context,
+ filter any,
+ opts ...options.Lister[options.RewrapManyDataKeyOptions],
+) (*RewrapManyDataKeyResult, error) {
+ // libmongocrypt versions 1.5.0 and 1.5.1 have a severe bug in RewrapManyDataKey.
+ // Check if the version string starts with 1.5.0 or 1.5.1. This accounts for pre-release versions, like 1.5.0-rc0.
+ if ce.closed {
+ return nil, ErrClientDisconnected
+ }
+
+ libmongocryptVersion := mongocrypt.Version()
+ if strings.HasPrefix(libmongocryptVersion, "1.5.0") || strings.HasPrefix(libmongocryptVersion, "1.5.1") {
+ return nil, fmt.Errorf("RewrapManyDataKey requires libmongocrypt 1.5.2 or newer. Detected version: %v", libmongocryptVersion)
+ }
+
+ if ctx == nil {
+ ctx = context.Background()
+ }
+
+ args, err := mongoutil.NewOptions[options.RewrapManyDataKeyOptions](opts...)
+ if err != nil {
+ return nil, fmt.Errorf("failed to construct options from builder: %w", err)
+ }
+
+ // Transfer rmdko options to /x/ package options to publish the mongocrypt feed.
+ co := &mcopts.RewrapManyDataKeyOptions{
+ Provider: args.Provider,
+ }
+ if args.MasterKey != nil {
+ keyDoc, err := marshal(
+ args.MasterKey,
+ ce.keyVaultClient.bsonOpts,
+ ce.keyVaultClient.registry)
+ if err != nil {
+ return nil, err
+ }
+ co.MasterKey = keyDoc
+ }
+
+ // Prepare the filters and rewrap the data key using mongocrypt.
+ filterdoc, err := marshal(filter, ce.keyVaultClient.bsonOpts, ce.keyVaultClient.registry)
+ if err != nil {
+ return nil, err
+ }
+
+ rewrappedDocuments, err := ce.crypt.RewrapDataKey(ctx, filterdoc, co)
+ if err != nil {
+ return nil, err
+ }
+ if len(rewrappedDocuments) == 0 {
+ // If there are no documents to rewrap, then do nothing.
+ return new(RewrapManyDataKeyResult), nil
+ }
+
+ // Prepare the WriteModel slice for bulk updating the rewrapped data keys.
+ models := []WriteModel{}
+ if err := setRewrapManyDataKeyWriteModels(rewrappedDocuments, &models); err != nil {
+ return nil, err
+ }
+
+ bulkWriteResults, err := ce.keyVaultColl.BulkWrite(ctx, models)
+ return &RewrapManyDataKeyResult{BulkWriteResult: bulkWriteResults}, err
+}
+
+// splitNamespace takes a namespace in the form "database.collection" and returns (database name, collection name)
+func splitNamespace(ns string) (string, string) {
+ firstDot := strings.Index(ns, ".")
+ if firstDot == -1 {
+ return "", ns
+ }
+
+ return ns[:firstDot], ns[firstDot+1:]
+}
diff --git a/vendor/go.mongodb.org/mongo-driver/v2/mongo/collection.go b/vendor/go.mongodb.org/mongo-driver/v2/mongo/collection.go
new file mode 100644
index 0000000..11c649c
--- /dev/null
+++ b/vendor/go.mongodb.org/mongo-driver/v2/mongo/collection.go
@@ -0,0 +1,2299 @@
+// Copyright (C) MongoDB, Inc. 2017-present.
+//
+// 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
+
+package mongo
+
+import (
+ "context"
+ "errors"
+ "fmt"
+ "reflect"
+ "strings"
+ "time"
+
+ "go.mongodb.org/mongo-driver/v2/bson"
+ "go.mongodb.org/mongo-driver/v2/internal/csfle"
+ "go.mongodb.org/mongo-driver/v2/internal/mongoutil"
+ "go.mongodb.org/mongo-driver/v2/internal/optionsutil"
+ "go.mongodb.org/mongo-driver/v2/internal/ptrutil"
+ "go.mongodb.org/mongo-driver/v2/internal/serverselector"
+ "go.mongodb.org/mongo-driver/v2/mongo/options"
+ "go.mongodb.org/mongo-driver/v2/mongo/readconcern"
+ "go.mongodb.org/mongo-driver/v2/mongo/readpref"
+ "go.mongodb.org/mongo-driver/v2/mongo/writeconcern"
+ "go.mongodb.org/mongo-driver/v2/x/bsonx/bsoncore"
+ "go.mongodb.org/mongo-driver/v2/x/mongo/driver"
+ "go.mongodb.org/mongo-driver/v2/x/mongo/driver/description"
+ "go.mongodb.org/mongo-driver/v2/x/mongo/driver/operation"
+ "go.mongodb.org/mongo-driver/v2/x/mongo/driver/session"
+)
+
+// Collection is a handle to a MongoDB collection. It is safe for concurrent use by multiple goroutines.
+type Collection struct {
+ client *Client
+ db *Database
+ name string
+ readConcern *readconcern.ReadConcern
+ writeConcern *writeconcern.WriteConcern
+ readPreference *readpref.ReadPref
+ readSelector description.ServerSelector
+ writeSelector description.ServerSelector
+ bsonOpts *options.BSONOptions
+ registry *bson.Registry
+}
+
+// aggregateParams is used to store information to configure an Aggregate operation.
+type aggregateParams struct {
+ ctx context.Context
+ pipeline any
+ client *Client
+ bsonOpts *options.BSONOptions
+ registry *bson.Registry
+ readConcern *readconcern.ReadConcern
+ writeConcern *writeconcern.WriteConcern
+ db string
+ col string
+ readSelector description.ServerSelector
+ writeSelector description.ServerSelector
+ readPreference *readpref.ReadPref
+}
+
+func closeImplicitSession(sess *session.Client) {
+ if sess != nil && sess.IsImplicit {
+ sess.EndSession()
+ }
+}
+
+func newCollection(db *Database, name string, opts ...options.Lister[options.CollectionOptions]) *Collection {
+ args, _ := mongoutil.NewOptions[options.CollectionOptions](opts...)
+
+ rc := db.readConcern
+ if args.ReadConcern != nil {
+ rc = args.ReadConcern
+ }
+
+ wc := db.writeConcern
+ if args.WriteConcern != nil {
+ wc = args.WriteConcern
+ }
+
+ rp := db.readPreference
+ if args.ReadPreference != nil {
+ rp = args.ReadPreference
+ }
+
+ bsonOpts := db.bsonOpts
+ if args.BSONOptions != nil {
+ bsonOpts = args.BSONOptions
+ }
+
+ reg := db.registry
+ if args.Registry != nil {
+ reg = args.Registry
+ }
+
+ readSelector := &serverselector.Composite{
+ Selectors: []description.ServerSelector{
+ &serverselector.ReadPref{ReadPref: rp},
+ &serverselector.Latency{Latency: db.client.localThreshold},
+ },
+ }
+
+ writeSelector := &serverselector.Composite{
+ Selectors: []description.ServerSelector{
+ &serverselector.Write{},
+ &serverselector.Latency{Latency: db.client.localThreshold},
+ },
+ }
+
+ coll := &Collection{
+ client: db.client,
+ db: db,
+ name: name,
+ readPreference: rp,
+ readConcern: rc,
+ writeConcern: wc,
+ readSelector: readSelector,
+ writeSelector: writeSelector,
+ bsonOpts: bsonOpts,
+ registry: reg,
+ }
+
+ return coll
+}
+
+func (coll *Collection) copy() *Collection {
+ return &Collection{
+ client: coll.client,
+ db: coll.db,
+ name: coll.name,
+ readConcern: coll.readConcern,
+ writeConcern: coll.writeConcern,
+ readPreference: coll.readPreference,
+ readSelector: coll.readSelector,
+ writeSelector: coll.writeSelector,
+ bsonOpts: coll.bsonOpts,
+ registry: coll.registry,
+ }
+}
+
+// Clone creates a copy of the Collection configured with the given CollectionOptions.
+// The specified options are merged with the existing options on the collection, with the specified options taking
+// precedence.
+func (coll *Collection) Clone(opts ...options.Lister[options.CollectionOptions]) *Collection {
+ copyColl := coll.copy()
+
+ args, _ := mongoutil.NewOptions[options.CollectionOptions](opts...)
+
+ if args.ReadConcern != nil {
+ copyColl.readConcern = args.ReadConcern
+ }
+
+ if args.WriteConcern != nil {
+ copyColl.writeConcern = args.WriteConcern
+ }
+
+ if args.ReadPreference != nil {
+ copyColl.readPreference = args.ReadPreference
+ }
+
+ if args.Registry != nil {
+ copyColl.registry = args.Registry
+ }
+
+ if args.BSONOptions != nil {
+ copyColl.bsonOpts = args.BSONOptions
+ }
+
+ copyColl.readSelector = &serverselector.Composite{
+ Selectors: []description.ServerSelector{
+ &serverselector.ReadPref{ReadPref: copyColl.readPreference},
+ &serverselector.Latency{Latency: copyColl.client.localThreshold},
+ },
+ }
+
+ return copyColl
+}
+
+// Name returns the name of the collection.
+func (coll *Collection) Name() string {
+ return coll.name
+}
+
+// Database returns the Database that was used to create the Collection.
+func (coll *Collection) Database() *Database {
+ return coll.db
+}
+
+// BulkWrite performs a bulk write operation (https://www.mongodb.com/docs/manual/core/bulk-write-operations/).
+//
+// The models parameter must be a slice of operations to be executed in this bulk write. It cannot be nil or empty.
+// All of the models must be non-nil. See the mongo.WriteModel documentation for a list of valid model types and
+// examples of how they should be used.
+//
+// The opts parameter can be used to specify options for the operation (see the options.BulkWriteOptions documentation.)
+func (coll *Collection) BulkWrite(ctx context.Context, models []WriteModel,
+ opts ...options.Lister[options.BulkWriteOptions],
+) (*BulkWriteResult, error) {
+ if len(models) == 0 {
+ return nil, fmt.Errorf("invalid models: %w", ErrEmptySlice)
+ }
+
+ if ctx == nil {
+ ctx = context.Background()
+ }
+
+ sess := sessionFromContext(ctx)
+ if sess == nil && coll.client.sessionPool != nil {
+ sess = session.NewImplicitClientSession(coll.client.sessionPool, coll.client.id)
+ defer sess.EndSession()
+ }
+
+ err := coll.client.validSession(sess)
+ if err != nil {
+ return nil, err
+ }
+
+ wc := coll.writeConcern
+ if sess.TransactionRunning() {
+ wc = nil
+ }
+ if !wc.Acknowledged() {
+ sess = nil
+ }
+
+ selector := makePinnedSelector(sess, coll.writeSelector)
+
+ for i, model := range models {
+ if model == nil {
+ return nil, fmt.Errorf("invalid model at index %d: %w", i, ErrNilDocument)
+ }
+ }
+
+ // Ensure opts have the default case at the front.
+ opts = append([]options.Lister[options.BulkWriteOptions]{options.BulkWrite()}, opts...)
+ args, err := mongoutil.NewOptions(opts...)
+ if err != nil {
+ return nil, err
+ }
+
+ op := bulkWrite{
+ comment: args.Comment,
+ ordered: args.Ordered,
+ bypassDocumentValidation: args.BypassDocumentValidation,
+ models: models,
+ session: sess,
+ collection: coll,
+ selector: selector,
+ writeConcern: wc,
+ let: args.Let,
+ }
+ if rawData, ok := optionsutil.Value(args.Internal, "rawData").(bool); ok {
+ op.rawData = &rawData
+ }
+ if additionalCmd, ok := optionsutil.Value(args.Internal, "addCommandFields").(bson.D); ok {
+ op.additionalCmd = additionalCmd
+ }
+
+ err = op.execute(ctx)
+
+ return &op.result, wrapErrors(err)
+}
+
+func (coll *Collection) insert(
+ ctx context.Context,
+ documents []any,
+ opts ...options.Lister[options.InsertManyOptions],
+) ([]any, error) {
+ if ctx == nil {
+ ctx = context.Background()
+ }
+
+ result := make([]any, len(documents))
+ docs := make([]bsoncore.Document, len(documents))
+
+ for i, doc := range documents {
+ bsoncoreDoc, err := marshal(doc, coll.bsonOpts, coll.registry)
+ if err != nil {
+ return nil, err
+ }
+ bsoncoreDoc, id, err := ensureID(bsoncoreDoc, bson.NilObjectID, coll.bsonOpts, coll.registry)
+ if err != nil {
+ return nil, err
+ }
+
+ docs[i] = bsoncoreDoc
+ result[i] = id
+ }
+
+ sess := sessionFromContext(ctx)
+ if sess == nil && coll.client.sessionPool != nil {
+ sess = session.NewImplicitClientSession(coll.client.sessionPool, coll.client.id)
+ defer sess.EndSession()
+ }
+
+ err := coll.client.validSession(sess)
+ if err != nil {
+ return nil, err
+ }
+
+ wc := coll.writeConcern
+ if sess.TransactionRunning() {
+ wc = nil
+ }
+ if !wc.Acknowledged() {
+ sess = nil
+ }
+
+ maxAdaptiveRetries := coll.client.effectiveAdaptiveRetries(coll.client.retryWrites)
+
+ selector := makePinnedSelector(sess, coll.writeSelector)
+
+ op := insert{
+ documents: docs,
+ session: sess,
+ writeConcern: wc,
+ monitor: coll.client.monitor,
+ maxAdaptiveRetries: maxAdaptiveRetries,
+ enableOverloadRetargeting: coll.client.enableOverloadRetargeting,
+ selector: selector,
+ clock: coll.client.clock,
+ database: coll.db.name,
+ collection: coll.name,
+ deployment: coll.client.deployment,
+ crypt: coll.client.cryptFLE,
+ ordered: ptrutil.Ptr(true),
+ serverAPI: coll.client.serverAPI,
+ timeout: coll.client.timeout,
+ logger: coll.client.logger,
+ authenticator: coll.client.authenticator,
+ }
+
+ args, err := mongoutil.NewOptions[options.InsertManyOptions](opts...)
+ if err != nil {
+ return nil, fmt.Errorf("failed to construct options from builder: %w", err)
+ }
+
+ if args.BypassDocumentValidation != nil && *args.BypassDocumentValidation {
+ op.bypassDocumentValidation = args.BypassDocumentValidation
+ }
+ if args.Comment != nil {
+ comment, err := marshalValue(args.Comment, coll.bsonOpts, coll.registry)
+ if err != nil {
+ return nil, err
+ }
+ op.comment = comment
+ }
+ if args.Ordered != nil {
+ op.ordered = args.Ordered
+ }
+ if rawData, ok := optionsutil.Value(args.Internal, "rawData").(bool); ok {
+ op.rawData = &rawData
+ }
+ if additionalCmd, ok := optionsutil.Value(args.Internal, "addCommandFields").(bson.D); ok {
+ op.additionalCmd = additionalCmd
+ }
+ retry := driver.RetryNone
+ if coll.client.retryWrites {
+ retry = driver.RetryOncePerCommand
+ }
+ op.retry = &retry
+
+ err = op.Execute(ctx)
+ var wce driver.WriteCommandError
+ if !errors.As(err, &wce) {
+ return result, err
+ }
+
+ // remove the ids that had writeErrors from result
+ for i, we := range wce.WriteErrors {
+ // i indexes have been removed before the current error, so the index is we.Index-i
+ idIndex := int(we.Index) - i
+ // if the insert is ordered, nothing after the error was inserted
+ if args.Ordered == nil || *args.Ordered {
+ result = result[:idIndex]
+ break
+ }
+ result = append(result[:idIndex], result[idIndex+1:]...)
+ }
+
+ return result, err
+}
+
+// InsertOne executes an insert command to insert a single document into the collection.
+//
+// The document parameter must be the document to be inserted. It cannot be nil. If the document does not have an _id
+// field when transformed into BSON, one will be added automatically to the marshalled document. The original document
+// will not be modified. The _id can be retrieved from the InsertedID field of the returned InsertOneResult.
+//
+// The opts parameter can be used to specify options for the operation (see the options.InsertOneOptions documentation.)
+//
+// For more information about the command, see https://www.mongodb.com/docs/manual/reference/command/insert/.
+func (coll *Collection) InsertOne(ctx context.Context, document any,
+ opts ...options.Lister[options.InsertOneOptions],
+) (*InsertOneResult, error) {
+ args, err := mongoutil.NewOptions(opts...)
+ if err != nil {
+ return nil, err
+ }
+ imOpts := options.InsertMany()
+
+ if args.BypassDocumentValidation != nil && *args.BypassDocumentValidation {
+ imOpts.SetBypassDocumentValidation(*args.BypassDocumentValidation)
+ }
+ if args.Comment != nil {
+ imOpts.SetComment(args.Comment)
+ }
+ if rawDataOpt := optionsutil.Value(args.Internal, "rawData"); rawDataOpt != nil {
+ imOpts.Opts = append(imOpts.Opts, func(opts *options.InsertManyOptions) error {
+ opts.Internal = optionsutil.WithValue(opts.Internal, "rawData", rawDataOpt)
+
+ return nil
+ })
+ }
+ if additionalCmd := optionsutil.Value(args.Internal, "addCommandFields"); additionalCmd != nil {
+ imOpts.Opts = append(imOpts.Opts, func(opts *options.InsertManyOptions) error {
+ opts.Internal = optionsutil.WithValue(opts.Internal, "addCommandFields", additionalCmd)
+
+ return nil
+ })
+ }
+ res, err := coll.insert(ctx, []any{document}, imOpts)
+
+ rr, err := processWriteError(err)
+ if rr&rrOne == 0 && rr.isAcknowledged() {
+ return nil, err
+ }
+
+ return &InsertOneResult{
+ InsertedID: res[0],
+ Acknowledged: rr.isAcknowledged(),
+ }, err
+}
+
+// InsertMany executes an insert command to insert multiple documents into the collection. If write errors occur
+// during the operation (e.g. duplicate key error), this method returns a BulkWriteException error.
+//
+// The documents parameter must be a slice of documents to insert. The slice cannot be nil or empty. The elements must
+// all be non-nil. For any document that does not have an _id field when transformed into BSON, one will be added
+// automatically to the marshalled document. The original document will not be modified. The _id values for the inserted
+// documents can be retrieved from the InsertedIDs field of the returned InsertManyResult.
+//
+// The opts parameter can be used to specify options for the operation (see the options.InsertManyOptions documentation.)
+//
+// For more information about the command, see https://www.mongodb.com/docs/manual/reference/command/insert/.
+func (coll *Collection) InsertMany(
+ ctx context.Context,
+ documents any,
+ opts ...options.Lister[options.InsertManyOptions],
+) (*InsertManyResult, error) {
+ dv := reflect.ValueOf(documents)
+ if dv.Kind() != reflect.Slice {
+ return nil, fmt.Errorf("invalid documents: %w", ErrNotSlice)
+ }
+ if dv.Len() == 0 {
+ return nil, fmt.Errorf("invalid documents: %w", ErrEmptySlice)
+ }
+
+ docSlice := make([]any, 0, dv.Len())
+ for i := 0; i < dv.Len(); i++ {
+ docSlice = append(docSlice, dv.Index(i).Interface())
+ }
+
+ result, err := coll.insert(ctx, docSlice, opts...)
+ rr, err := processWriteError(err)
+ if rr&rrMany == 0 {
+ return nil, err
+ }
+
+ imResult := &InsertManyResult{
+ InsertedIDs: result,
+ Acknowledged: rr.isAcknowledged(),
+ }
+ var writeException WriteException
+ if !errors.As(err, &writeException) {
+ return imResult, err
+ }
+
+ // create and return a BulkWriteException
+ bwErrors := make([]BulkWriteError, 0, len(writeException.WriteErrors))
+ for _, we := range writeException.WriteErrors {
+ bwErrors = append(bwErrors, BulkWriteError{
+ WriteError: we,
+ Request: nil,
+ })
+ }
+
+ return imResult, BulkWriteException{
+ WriteErrors: bwErrors,
+ WriteConcernError: writeException.WriteConcernError,
+ Labels: writeException.Labels,
+ }
+}
+
+func (coll *Collection) delete(
+ ctx context.Context,
+ filter any,
+ deleteOne bool,
+ expectedRr returnResult,
+ args *options.DeleteManyOptions,
+) (*DeleteResult, error) {
+ if ctx == nil {
+ ctx = context.Background()
+ }
+
+ f, err := marshal(filter, coll.bsonOpts, coll.registry)
+ if err != nil {
+ return nil, err
+ }
+
+ sess := sessionFromContext(ctx)
+ if sess == nil && coll.client.sessionPool != nil {
+ sess = session.NewImplicitClientSession(coll.client.sessionPool, coll.client.id)
+ defer sess.EndSession()
+ }
+
+ err = coll.client.validSession(sess)
+ if err != nil {
+ return nil, err
+ }
+
+ wc := coll.writeConcern
+ if sess.TransactionRunning() {
+ wc = nil
+ }
+ if !wc.Acknowledged() {
+ sess = nil
+ }
+
+ // deleteMany cannot be retried
+ retryMode := driver.RetryNone
+ if deleteOne && coll.client.retryWrites {
+ retryMode = driver.RetryOncePerCommand
+ }
+
+ maxAdaptiveRetries := coll.client.effectiveAdaptiveRetries(coll.client.retryWrites)
+
+ selector := makePinnedSelector(sess, coll.writeSelector)
+
+ var limit int32
+ if deleteOne {
+ limit = 1
+ }
+
+ didx, doc := bsoncore.AppendDocumentStart(nil)
+ doc = bsoncore.AppendDocumentElement(doc, "q", f)
+ doc = bsoncore.AppendInt32Element(doc, "limit", limit)
+ if args.Collation != nil {
+ doc = bsoncore.AppendDocumentElement(doc, "collation", toDocument(args.Collation))
+ }
+ if args.Hint != nil {
+ if isUnorderedMap(args.Hint) {
+ return nil, ErrMapForOrderedArgument{"hint"}
+ }
+ hint, err := marshalValue(args.Hint, coll.bsonOpts, coll.registry)
+ if err != nil {
+ return nil, err
+ }
+
+ doc = bsoncore.AppendValueElement(doc, "hint", hint)
+ }
+ doc, _ = bsoncore.AppendDocumentEnd(doc, didx)
+
+ op := operation.NewDelete(doc).
+ Session(sess).WriteConcern(wc).CommandMonitor(coll.client.monitor).
+ Retry(retryMode).MaxAdaptiveRetries(maxAdaptiveRetries).
+ EnableOverloadRetargeting(coll.client.enableOverloadRetargeting).
+ ServerSelector(selector).ClusterClock(coll.client.clock).
+ Database(coll.db.name).Collection(coll.name).
+ Deployment(coll.client.deployment).Crypt(coll.client.cryptFLE).Ordered(true).
+ ServerAPI(coll.client.serverAPI).Timeout(coll.client.timeout).Logger(coll.client.logger).Authenticator(coll.client.authenticator)
+ if args.Comment != nil {
+ comment, err := marshalValue(args.Comment, coll.bsonOpts, coll.registry)
+ if err != nil {
+ return nil, err
+ }
+ op = op.Comment(comment)
+ }
+ if args.Hint != nil {
+ op = op.Hint(true)
+ }
+ if args.Let != nil {
+ let, err := marshal(args.Let, coll.bsonOpts, coll.registry)
+ if err != nil {
+ return nil, err
+ }
+ op = op.Let(let)
+ }
+ if rawData, ok := optionsutil.Value(args.Internal, "rawData").(bool); ok {
+ op = op.RawData(rawData)
+ }
+
+ rr, err := processWriteError(op.Execute(ctx))
+ if rr&expectedRr == 0 {
+ return nil, err
+ }
+ return &DeleteResult{
+ DeletedCount: op.Result().N,
+ Acknowledged: rr.isAcknowledged(),
+ }, err
+}
+
+// DeleteOne executes a delete command to delete at most one document from the collection.
+//
+// The filter parameter must be a document containing query operators and can be used to select the document to be
+// deleted. It cannot be nil. If the filter does not match any documents, the operation will succeed and a DeleteResult
+// with a DeletedCount of 0 will be returned. If the filter matches multiple documents, one will be selected from the
+// matched set.
+//
+// The opts parameter can be used to specify options for the operation (see the options.DeleteOptions documentation).
+//
+// For more information about the command, see https://www.mongodb.com/docs/manual/reference/command/delete/.
+func (coll *Collection) DeleteOne(
+ ctx context.Context,
+ filter any,
+ opts ...options.Lister[options.DeleteOneOptions],
+) (*DeleteResult, error) {
+ args, err := mongoutil.NewOptions[options.DeleteOneOptions](opts...)
+ if err != nil {
+ return nil, fmt.Errorf("failed to construct options from builder: %w", err)
+ }
+ deleteOptions := &options.DeleteManyOptions{
+ Collation: args.Collation,
+ Comment: args.Comment,
+ Hint: args.Hint,
+ Let: args.Let,
+ Internal: args.Internal,
+ }
+
+ return coll.delete(ctx, filter, true, rrOne, deleteOptions)
+}
+
+// DeleteMany executes a delete command to delete documents from the collection.
+//
+// The filter parameter must be a document containing query operators and can be used to select the documents to
+// be deleted. It cannot be nil. An empty document (e.g. bson.D{}) should be used to delete all documents in the
+// collection. If the filter does not match any documents, the operation will succeed and a DeleteResult with a
+// DeletedCount of 0 will be returned.
+//
+// The opts parameter can be used to specify options for the operation (see the options.DeleteOptions documentation).
+//
+// For more information about the command, see https://www.mongodb.com/docs/manual/reference/command/delete/.
+func (coll *Collection) DeleteMany(
+ ctx context.Context,
+ filter any,
+ opts ...options.Lister[options.DeleteManyOptions],
+) (*DeleteResult, error) {
+ args, err := mongoutil.NewOptions[options.DeleteManyOptions](opts...)
+ if err != nil {
+ return nil, fmt.Errorf("failed to construct options from builder: %w", err)
+ }
+
+ return coll.delete(ctx, filter, false, rrMany, args)
+}
+
+func (coll *Collection) updateOrReplace(
+ ctx context.Context,
+ filter bsoncore.Document,
+ update any,
+ multi bool,
+ expectedRr returnResult,
+ checkDollarKey bool,
+ sort any,
+ args *options.UpdateManyOptions,
+) (*UpdateResult, error) {
+ if ctx == nil {
+ ctx = context.Background()
+ }
+
+ // collation, arrayFilters, upsert, and hint are included on the individual update documents rather than as part of the
+ // command
+ updateDoc, err := updateDoc{
+ filter: filter,
+ update: update,
+ hint: args.Hint,
+ sort: sort,
+ arrayFilters: args.ArrayFilters,
+ collation: args.Collation,
+ upsert: args.Upsert,
+ multi: multi,
+ checkDollarKey: checkDollarKey,
+ }.marshal(coll.bsonOpts, coll.registry)
+ if err != nil {
+ return nil, err
+ }
+
+ sess := sessionFromContext(ctx)
+ if sess == nil && coll.client.sessionPool != nil {
+ sess = session.NewImplicitClientSession(coll.client.sessionPool, coll.client.id)
+ defer sess.EndSession()
+ }
+
+ err = coll.client.validSession(sess)
+ if err != nil {
+ return nil, err
+ }
+
+ wc := coll.writeConcern
+ if sess.TransactionRunning() {
+ wc = nil
+ }
+ if !wc.Acknowledged() {
+ sess = nil
+ }
+
+ retry := driver.RetryNone
+ // retryable writes are only enabled updateOne/replaceOne operations
+ if !multi && coll.client.retryWrites {
+ retry = driver.RetryOncePerCommand
+ }
+
+ maxAdaptiveRetries := coll.client.effectiveAdaptiveRetries(coll.client.retryWrites)
+
+ selector := makePinnedSelector(sess, coll.writeSelector)
+
+ op := operation.NewUpdate(updateDoc).
+ Session(sess).WriteConcern(wc).CommandMonitor(coll.client.monitor).
+ Retry(retry).MaxAdaptiveRetries(maxAdaptiveRetries).
+ EnableOverloadRetargeting(coll.client.enableOverloadRetargeting).
+ ServerSelector(selector).ClusterClock(coll.client.clock).
+ Database(coll.db.name).Collection(coll.name).
+ Deployment(coll.client.deployment).Crypt(coll.client.cryptFLE).Hint(args.Hint != nil).
+ ArrayFilters(args.ArrayFilters != nil).Ordered(true).ServerAPI(coll.client.serverAPI).
+ Timeout(coll.client.timeout).Logger(coll.client.logger).Authenticator(coll.client.authenticator)
+ if args.Let != nil {
+ let, err := marshal(args.Let, coll.bsonOpts, coll.registry)
+ if err != nil {
+ return nil, err
+ }
+ op = op.Let(let)
+ }
+
+ if args.BypassDocumentValidation != nil && *args.BypassDocumentValidation {
+ op = op.BypassDocumentValidation(*args.BypassDocumentValidation)
+ }
+ if args.Comment != nil {
+ comment, err := marshalValue(args.Comment, coll.bsonOpts, coll.registry)
+ if err != nil {
+ return nil, err
+ }
+ op = op.Comment(comment)
+ }
+ if rawData, ok := optionsutil.Value(args.Internal, "rawData").(bool); ok {
+ op = op.RawData(rawData)
+ }
+ if additionalCmd, ok := optionsutil.Value(args.Internal, "addCommandFields").(bson.D); ok {
+ op = op.AdditionalCmd(additionalCmd)
+ }
+ err = op.Execute(ctx)
+
+ rr, err := processWriteError(err)
+ if rr&expectedRr == 0 {
+ return nil, err
+ }
+
+ opRes := op.Result()
+ res := &UpdateResult{
+ MatchedCount: opRes.N,
+ ModifiedCount: opRes.NModified,
+ UpsertedCount: int64(len(opRes.Upserted)),
+ Acknowledged: rr.isAcknowledged(),
+ }
+ if len(opRes.Upserted) > 0 {
+ res.UpsertedID = opRes.Upserted[0].ID
+ res.MatchedCount--
+ }
+
+ return res, err
+}
+
+// UpdateByID executes an update command to update the document whose _id value matches the provided ID in the collection.
+// This is equivalent to running UpdateOne(ctx, bson.D{{"_id", id}}, update, opts...).
+//
+// The id parameter is the _id of the document to be updated. It cannot be nil. If the ID does not match any documents,
+// the operation will succeed and an UpdateResult with a MatchedCount of 0 will be returned.
+//
+// The update parameter must be a document containing update operators
+// (https://www.mongodb.com/docs/manual/reference/operator/update/) and can be used to specify the modifications to be
+// made to the selected document. It cannot be nil or empty.
+//
+// The opts parameter can be used to specify options for the operation (see the options.UpdateOptions documentation).
+//
+// For more information about the command, see https://www.mongodb.com/docs/manual/reference/command/update/.
+func (coll *Collection) UpdateByID(
+ ctx context.Context,
+ id any,
+ update any,
+ opts ...options.Lister[options.UpdateOneOptions],
+) (*UpdateResult, error) {
+ if id == nil {
+ return nil, fmt.Errorf("invalid id: %w", ErrNilValue)
+ }
+ return coll.UpdateOne(ctx, bson.D{{"_id", id}}, update, opts...)
+}
+
+// UpdateOne executes an update command to update at most one document in the collection.
+//
+// The filter parameter must be a document containing query operators and can be used to select the document to be
+// updated. It cannot be nil. If the filter does not match any documents, the operation will succeed and an UpdateResult
+// with a MatchedCount of 0 will be returned. If the filter matches multiple documents, one will be selected from the
+// matched set and MatchedCount will equal 1.
+//
+// The update parameter must be a document containing update operators
+// (https://www.mongodb.com/docs/manual/reference/operator/update/) and can be used to specify the modifications to be
+// made to the selected document. It cannot be nil or empty.
+//
+// The opts parameter can be used to specify options for the operation (see the options.UpdateOptions documentation).
+//
+// For more information about the command, see https://www.mongodb.com/docs/manual/reference/command/update/.
+func (coll *Collection) UpdateOne(
+ ctx context.Context,
+ filter any,
+ update any,
+ opts ...options.Lister[options.UpdateOneOptions],
+) (*UpdateResult, error) {
+ if ctx == nil {
+ ctx = context.Background()
+ }
+
+ f, err := marshal(filter, coll.bsonOpts, coll.registry)
+ if err != nil {
+ return nil, err
+ }
+
+ args, err := mongoutil.NewOptions[options.UpdateOneOptions](opts...)
+ if err != nil {
+ return nil, fmt.Errorf("failed to construct options from builder: %w", err)
+ }
+ updateOptions := &options.UpdateManyOptions{
+ ArrayFilters: args.ArrayFilters,
+ BypassDocumentValidation: args.BypassDocumentValidation,
+ Collation: args.Collation,
+ Comment: args.Comment,
+ Hint: args.Hint,
+ Upsert: args.Upsert,
+ Let: args.Let,
+ Internal: args.Internal,
+ }
+
+ return coll.updateOrReplace(ctx, f, update, false, rrOne, true, args.Sort, updateOptions)
+}
+
+// UpdateMany executes an update command to update documents in the collection.
+//
+// The filter parameter must be a document containing query operators and can be used to select the documents to be
+// updated. It cannot be nil. If the filter does not match any documents, the operation will succeed and an UpdateResult
+// with a MatchedCount of 0 will be returned.
+//
+// The update parameter must be a document containing update operators
+// (https://www.mongodb.com/docs/manual/reference/operator/update/) and can be used to specify the modifications to be made
+// to the selected documents. It cannot be nil or empty.
+//
+// The opts parameter can be used to specify options for the operation (see the options.UpdateOptions documentation).
+//
+// For more information about the command, see https://www.mongodb.com/docs/manual/reference/command/update/.
+func (coll *Collection) UpdateMany(
+ ctx context.Context,
+ filter any,
+ update any,
+ opts ...options.Lister[options.UpdateManyOptions],
+) (*UpdateResult, error) {
+ if ctx == nil {
+ ctx = context.Background()
+ }
+
+ f, err := marshal(filter, coll.bsonOpts, coll.registry)
+ if err != nil {
+ return nil, err
+ }
+
+ args, err := mongoutil.NewOptions[options.UpdateManyOptions](opts...)
+ if err != nil {
+ return nil, fmt.Errorf("failed to construct options from builder: %w", err)
+ }
+
+ return coll.updateOrReplace(ctx, f, update, true, rrMany, true, nil, args)
+}
+
+// ReplaceOne executes an update command to replace at most one document in the collection.
+//
+// The filter parameter must be a document containing query operators and can be used to select the document to be
+// replaced. It cannot be nil. If the filter does not match any documents, the operation will succeed and an
+// UpdateResult with a MatchedCount of 0 will be returned. If the filter matches multiple documents, one will be
+// selected from the matched set and MatchedCount will equal 1.
+//
+// The replacement parameter must be a document that will be used to replace the selected document. It cannot be nil
+// and cannot contain any update operators (https://www.mongodb.com/docs/manual/reference/operator/update/).
+//
+// The opts parameter can be used to specify options for the operation (see the options.ReplaceOptions documentation).
+//
+// For more information about the command, see https://www.mongodb.com/docs/manual/reference/command/update/.
+func (coll *Collection) ReplaceOne(
+ ctx context.Context,
+ filter any,
+ replacement any,
+ opts ...options.Lister[options.ReplaceOptions],
+) (*UpdateResult, error) {
+ if ctx == nil {
+ ctx = context.Background()
+ }
+
+ args, err := mongoutil.NewOptions[options.ReplaceOptions](opts...)
+ if err != nil {
+ return nil, fmt.Errorf("failed to construct options from builder: %w", err)
+ }
+
+ f, err := marshal(filter, coll.bsonOpts, coll.registry)
+ if err != nil {
+ return nil, err
+ }
+
+ r, err := marshal(replacement, coll.bsonOpts, coll.registry)
+ if err != nil {
+ return nil, err
+ }
+
+ if err := ensureNoDollarKey(r); err != nil {
+ return nil, err
+ }
+
+ updateOptions := &options.UpdateManyOptions{
+ BypassDocumentValidation: args.BypassDocumentValidation,
+ Collation: args.Collation,
+ Upsert: args.Upsert,
+ Hint: args.Hint,
+ Let: args.Let,
+ Comment: args.Comment,
+ Internal: args.Internal,
+ }
+
+ return coll.updateOrReplace(ctx, f, r, false, rrOne, false, args.Sort, updateOptions)
+}
+
+// Aggregate executes an aggregate command against the collection and returns a cursor over the resulting documents.
+//
+// The pipeline parameter must be an array of documents, each representing an aggregation stage. The pipeline cannot
+// be nil but can be empty. The stage documents must all be non-nil. For a pipeline of bson.D documents, the
+// mongo.Pipeline type can be used. See
+// https://www.mongodb.com/docs/manual/reference/operator/aggregation-pipeline/#db-collection-aggregate-stages for a list of
+// valid stages in aggregations.
+//
+// The opts parameter can be used to specify options for the operation (see the options.AggregateOptions documentation.)
+//
+// For more information about the command, see https://www.mongodb.com/docs/manual/reference/command/aggregate/.
+func (coll *Collection) Aggregate(
+ ctx context.Context,
+ pipeline any,
+ opts ...options.Lister[options.AggregateOptions],
+) (*Cursor, error) {
+ a := aggregateParams{
+ ctx: ctx,
+ pipeline: pipeline,
+ client: coll.client,
+ registry: coll.registry,
+ readConcern: coll.readConcern,
+ writeConcern: coll.writeConcern,
+ bsonOpts: coll.bsonOpts,
+ db: coll.db.name,
+ col: coll.name,
+ readSelector: coll.readSelector,
+ writeSelector: coll.writeSelector,
+ readPreference: coll.readPreference,
+ }
+
+ return aggregate(a, opts...)
+}
+
+// aggregate is the helper method for Aggregate
+func aggregate(a aggregateParams, opts ...options.Lister[options.AggregateOptions]) (cur *Cursor, err error) {
+ if a.ctx == nil {
+ a.ctx = context.Background()
+ }
+
+ pipelineArr, hasOutputStage, err := marshalAggregatePipeline(a.pipeline, a.bsonOpts, a.registry)
+ if err != nil {
+ return nil, err
+ }
+
+ sess := sessionFromContext(a.ctx)
+ // Always close any created implicit sessions if aggregate returns an error.
+ defer func() {
+ if err != nil && sess != nil {
+ closeImplicitSession(sess)
+ }
+ }()
+ if sess == nil && a.client.sessionPool != nil {
+ sess = session.NewImplicitClientSession(a.client.sessionPool, a.client.id)
+ }
+
+ if err = a.client.validSession(sess); err != nil {
+ return nil, err
+ }
+
+ var wc *writeconcern.WriteConcern
+ if hasOutputStage {
+ wc = a.writeConcern
+ }
+ rc := a.readConcern
+ if sess.TransactionRunning() {
+ wc = nil
+ rc = nil
+ }
+ if !wc.Acknowledged() {
+ closeImplicitSession(sess)
+ sess = nil
+ }
+
+ retryReads := a.client.retryReads && !hasOutputStage
+ retryWrites := a.client.retryWrites && hasOutputStage
+
+ retry := driver.RetryNone
+ if retryReads {
+ retry = driver.RetryOncePerCommand
+ }
+
+ selector := makeReadPrefSelector(sess, a.readSelector, a.client.localThreshold)
+ if hasOutputStage {
+ selector = makeOutputAggregateSelector(sess, a.readPreference, a.client.localThreshold)
+ }
+
+ args, err := mongoutil.NewOptions(opts...)
+ if err != nil {
+ return nil, err
+ }
+
+ cursorOpts := a.client.createBaseCursorOptions(retryReads || retryWrites)
+
+ cursorOpts.MarshalValueEncoderFn = newEncoderFn(a.bsonOpts, a.registry)
+
+ op := operation.NewAggregate(pipelineArr).
+ Session(sess).
+ WriteConcern(wc).
+ ReadConcern(rc).
+ ReadPreference(a.readPreference).
+ CommandMonitor(a.client.monitor).
+ Retry(retry).
+ MaxAdaptiveRetries(cursorOpts.MaxAdaptiveRetries).
+ EnableOverloadRetargeting(cursorOpts.EnableOverloadRetargeting).
+ ServerSelector(selector).
+ ClusterClock(a.client.clock).
+ Database(a.db).
+ Collection(a.col).
+ Deployment(a.client.deployment).
+ Crypt(a.client.cryptFLE).
+ ServerAPI(a.client.serverAPI).
+ HasOutputStage(hasOutputStage).
+ Timeout(a.client.timeout).
+ Authenticator(a.client.authenticator).
+ // Omit "maxTimeMS" from operations that return a user-managed cursor to
+ // prevent confusing "cursor not found" errors.
+ //
+ // See DRIVERS-2722 for more detail.
+ OmitMaxTimeMS(true)
+
+ if args.AllowDiskUse != nil {
+ op.AllowDiskUse(*args.AllowDiskUse)
+ }
+ // ignore batchSize of 0 with $out
+ if args.BatchSize != nil && (*args.BatchSize != 0 || !hasOutputStage) {
+ op.BatchSize(*args.BatchSize)
+ cursorOpts.BatchSize = *args.BatchSize
+ }
+ if args.BypassDocumentValidation != nil && *args.BypassDocumentValidation {
+ op.BypassDocumentValidation(*args.BypassDocumentValidation)
+ }
+ if args.Collation != nil {
+ op.Collation(bsoncore.Document(toDocument(args.Collation)))
+ }
+ if args.MaxAwaitTime != nil {
+ cursorOpts.SetMaxAwaitTime(*args.MaxAwaitTime)
+ }
+ if args.Comment != nil {
+ comment, err := marshalValue(args.Comment, a.bsonOpts, a.registry)
+ if err != nil {
+ return nil, err
+ }
+
+ op.Comment(comment)
+ cursorOpts.Comment = comment
+ }
+ if args.Hint != nil {
+ if isUnorderedMap(args.Hint) {
+ return nil, ErrMapForOrderedArgument{"hint"}
+ }
+ hintVal, err := marshalValue(args.Hint, a.bsonOpts, a.registry)
+ if err != nil {
+ return nil, err
+ }
+ op.Hint(hintVal)
+ }
+ if args.Let != nil {
+ let, err := marshal(args.Let, a.bsonOpts, a.registry)
+ if err != nil {
+ return nil, err
+ }
+ op.Let(let)
+ }
+ if args.Custom != nil {
+ // Marshal all custom options before passing to the aggregate operation. Return
+ // any errors from Marshaling.
+ customOptions := make(map[string]bsoncore.Value)
+ for optionName, optionValue := range args.Custom {
+ optionValueBSON, err := marshalValue(optionValue, nil, a.registry)
+ if err != nil {
+ return nil, err
+ }
+ customOptions[optionName] = optionValueBSON
+ }
+ op.CustomOptions(customOptions)
+ }
+ if rawData, ok := optionsutil.Value(args.Internal, "rawData").(bool); ok {
+ op = op.RawData(rawData)
+ }
+
+ err = op.Execute(a.ctx)
+ if err != nil {
+ var wce driver.WriteCommandError
+ if errors.As(err, &wce) && wce.WriteConcernError != nil {
+ return nil, *convertDriverWriteConcernError(wce.WriteConcernError)
+ }
+ return nil, wrapErrors(err)
+ }
+
+ bc, err := op.Result(cursorOpts)
+ if err != nil {
+ return nil, wrapErrors(err)
+ }
+ cursor, err := newCursorWithSession(bc, a.client.bsonOpts, a.registry, sess,
+
+ // The only way the server will return a tailable/awaitData cursor for an
+ // aggregate operation is for the first stage in the pipeline to
+ // be $changeStream, this is the only time maxAwaitTimeMS should be applied.
+ // For this reason, we pass the client timeout to the cursor.
+ withCursorOptionClientTimeout(a.client.timeout))
+ return cursor, wrapErrors(err)
+}
+
+// CountDocuments returns the number of documents in the collection. For a fast count of the documents in the
+// collection, see the EstimatedDocumentCount method.
+//
+// The filter parameter must be a document and can be used to select which documents contribute to the count. It
+// cannot be nil. An empty document (e.g. bson.D{}) should be used to count all documents in the collection. This will
+// result in a full collection scan.
+//
+// The opts parameter can be used to specify options for the operation (see the options.CountOptions documentation).
+func (coll *Collection) CountDocuments(ctx context.Context, filter any,
+ opts ...options.Lister[options.CountOptions],
+) (int64, error) {
+ if ctx == nil {
+ ctx = context.Background()
+ }
+
+ args, err := mongoutil.NewOptions[options.CountOptions](opts...)
+ if err != nil {
+ return 0, err
+ }
+
+ pipelineArr, err := countDocumentsAggregatePipeline(filter, coll.bsonOpts, coll.registry, args)
+ if err != nil {
+ return 0, err
+ }
+
+ sess := sessionFromContext(ctx)
+ if sess == nil && coll.client.sessionPool != nil {
+ sess = session.NewImplicitClientSession(coll.client.sessionPool, coll.client.id)
+ defer sess.EndSession()
+ }
+ if err = coll.client.validSession(sess); err != nil {
+ return 0, err
+ }
+
+ rc := coll.readConcern
+ if sess.TransactionRunning() {
+ rc = nil
+ }
+
+ retry := driver.RetryNone
+ if coll.client.retryReads {
+ retry = driver.RetryOncePerCommand
+ }
+
+ maxAdaptiveRetries := coll.client.effectiveAdaptiveRetries(coll.client.retryReads)
+
+ selector := makeReadPrefSelector(sess, coll.readSelector, coll.client.localThreshold)
+ op := operation.NewAggregate(pipelineArr).Session(sess).ReadConcern(rc).ReadPreference(coll.readPreference).
+ Retry(retry).MaxAdaptiveRetries(maxAdaptiveRetries).
+ EnableOverloadRetargeting(coll.client.enableOverloadRetargeting).
+ CommandMonitor(coll.client.monitor).ServerSelector(selector).ClusterClock(coll.client.clock).Database(coll.db.name).
+ Collection(coll.name).Deployment(coll.client.deployment).Crypt(coll.client.cryptFLE).ServerAPI(coll.client.serverAPI).
+ Timeout(coll.client.timeout).Authenticator(coll.client.authenticator)
+ if args.Collation != nil {
+ op.Collation(bsoncore.Document(toDocument(args.Collation)))
+ }
+ if args.Comment != nil {
+ comment, err := marshalValue(args.Comment, coll.bsonOpts, coll.registry)
+ if err != nil {
+ return 0, err
+ }
+
+ op.Comment(comment)
+ }
+ if args.Hint != nil {
+ if isUnorderedMap(args.Hint) {
+ return 0, ErrMapForOrderedArgument{"hint"}
+ }
+ hintVal, err := marshalValue(args.Hint, coll.bsonOpts, coll.registry)
+ if err != nil {
+ return 0, err
+ }
+ op.Hint(hintVal)
+ }
+ if rawData, ok := optionsutil.Value(args.Internal, "rawData").(bool); ok {
+ op = op.RawData(rawData)
+ }
+
+ err = op.Execute(ctx)
+ if err != nil {
+ return 0, wrapErrors(err)
+ }
+
+ batch := op.ResultCursorResponse().FirstBatch
+ if batch == nil {
+ return 0, errors.New("invalid response from server, no 'firstBatch' field")
+ }
+
+ docs, err := batch.Documents()
+ if err != nil || len(docs) == 0 {
+ return 0, nil
+ }
+
+ val, ok := docs[0].Lookup("n").AsInt64OK()
+ if !ok {
+ return 0, errors.New("invalid response from server, no 'n' field")
+ }
+
+ return val, nil
+}
+
+// EstimatedDocumentCount executes a count command and returns an estimate of the number of documents in the collection
+// using collection metadata.
+//
+// The opts parameter can be used to specify options for the operation (see the options.EstimatedDocumentCountOptions
+// documentation).
+//
+// For more information about the command, see https://www.mongodb.com/docs/manual/reference/command/count/.
+func (coll *Collection) EstimatedDocumentCount(
+ ctx context.Context,
+ opts ...options.Lister[options.EstimatedDocumentCountOptions],
+) (int64, error) {
+ if ctx == nil {
+ ctx = context.Background()
+ }
+
+ sess := sessionFromContext(ctx)
+
+ var err error
+ if sess == nil && coll.client.sessionPool != nil {
+ sess = session.NewImplicitClientSession(coll.client.sessionPool, coll.client.id)
+ defer sess.EndSession()
+ }
+
+ err = coll.client.validSession(sess)
+ if err != nil {
+ return 0, err
+ }
+
+ rc := coll.readConcern
+ if sess.TransactionRunning() {
+ rc = nil
+ }
+
+ args, err := mongoutil.NewOptions[options.EstimatedDocumentCountOptions](opts...)
+ if err != nil {
+ return 0, fmt.Errorf("failed to construct options from builder: %w", err)
+ }
+
+ retry := driver.RetryNone
+ if coll.client.retryReads {
+ retry = driver.RetryOncePerCommand
+ }
+
+ maxAdaptiveRetries := coll.client.effectiveAdaptiveRetries(coll.client.retryReads)
+
+ selector := makeReadPrefSelector(sess, coll.readSelector, coll.client.localThreshold)
+ op := operation.NewCount().Session(sess).ClusterClock(coll.client.clock).
+ Database(coll.db.name).Collection(coll.name).CommandMonitor(coll.client.monitor).
+ Deployment(coll.client.deployment).ReadConcern(rc).ReadPreference(coll.readPreference).
+ Retry(retry).MaxAdaptiveRetries(maxAdaptiveRetries).
+ EnableOverloadRetargeting(coll.client.enableOverloadRetargeting).
+ ServerSelector(selector).Crypt(coll.client.cryptFLE).ServerAPI(coll.client.serverAPI).
+ Timeout(coll.client.timeout).Authenticator(coll.client.authenticator)
+
+ if args.Comment != nil {
+ comment, err := marshalValue(args.Comment, coll.bsonOpts, coll.registry)
+ if err != nil {
+ return 0, err
+ }
+ op = op.Comment(comment)
+ }
+ if rawData, ok := optionsutil.Value(args.Internal, "rawData").(bool); ok {
+ op = op.RawData(rawData)
+ }
+
+ err = op.Execute(ctx)
+ return op.Result().N, wrapErrors(err)
+}
+
+// Distinct executes a distinct command to find the unique values for a specified field in the collection.
+//
+// The fieldName parameter specifies the field name for which distinct values should be returned.
+//
+// The filter parameter must be a document containing query operators and can be used to select which documents are
+// considered. It cannot be nil. An empty document (e.g. bson.D{}) should be used to select all documents.
+//
+// The opts parameter can be used to specify options for the operation (see the options.DistinctOptions documentation).
+//
+// For more information about the command, see https://www.mongodb.com/docs/manual/reference/command/distinct/.
+func (coll *Collection) Distinct(
+ ctx context.Context,
+ fieldName string,
+ filter any,
+ opts ...options.Lister[options.DistinctOptions],
+) *DistinctResult {
+ if ctx == nil {
+ ctx = context.Background()
+ }
+
+ f, err := marshal(filter, coll.bsonOpts, coll.registry)
+ if err != nil {
+ return &DistinctResult{err: err}
+ }
+
+ sess := sessionFromContext(ctx)
+
+ if sess == nil && coll.client.sessionPool != nil {
+ sess = session.NewImplicitClientSession(coll.client.sessionPool, coll.client.id)
+ defer sess.EndSession()
+ }
+
+ err = coll.client.validSession(sess)
+ if err != nil {
+ return &DistinctResult{err: err}
+ }
+
+ rc := coll.readConcern
+ if sess.TransactionRunning() {
+ rc = nil
+ }
+
+ retry := driver.RetryNone
+ if coll.client.retryReads {
+ retry = driver.RetryOncePerCommand
+ }
+
+ maxAdaptiveRetries := coll.client.effectiveAdaptiveRetries(coll.client.retryReads)
+
+ selector := makeReadPrefSelector(sess, coll.readSelector, coll.client.localThreshold)
+
+ args, err := mongoutil.NewOptions[options.DistinctOptions](opts...)
+ if err != nil {
+ err = fmt.Errorf("failed to construct options from builder: %w", err)
+
+ return &DistinctResult{err: err}
+ }
+
+ op := operation.NewDistinct(fieldName, f).
+ Session(sess).ClusterClock(coll.client.clock).
+ Database(coll.db.name).Collection(coll.name).CommandMonitor(coll.client.monitor).
+ Deployment(coll.client.deployment).ReadConcern(rc).ReadPreference(coll.readPreference).
+ Retry(retry).MaxAdaptiveRetries(maxAdaptiveRetries).
+ EnableOverloadRetargeting(coll.client.enableOverloadRetargeting).
+ ServerSelector(selector).Crypt(coll.client.cryptFLE).ServerAPI(coll.client.serverAPI).
+ Timeout(coll.client.timeout).Authenticator(coll.client.authenticator)
+
+ if args.Collation != nil {
+ op.Collation(bsoncore.Document(toDocument(args.Collation)))
+ }
+ if args.Comment != nil {
+ comment, err := marshalValue(args.Comment, coll.bsonOpts, coll.registry)
+ if err != nil {
+ return &DistinctResult{err: err}
+ }
+ op.Comment(comment)
+ }
+ if args.Hint != nil {
+ if isUnorderedMap(args.Hint) {
+ return &DistinctResult{err: ErrMapForOrderedArgument{"hint"}}
+ }
+ hint, err := marshalValue(args.Hint, coll.bsonOpts, coll.registry)
+ if err != nil {
+ return &DistinctResult{err: err}
+ }
+ op.Hint(hint)
+ }
+ if rawData, ok := optionsutil.Value(args.Internal, "rawData").(bool); ok {
+ op = op.RawData(rawData)
+ }
+
+ err = op.Execute(ctx)
+ if err != nil {
+ return &DistinctResult{err: wrapErrors(err)}
+ }
+
+ arr, ok := op.Result().Values.ArrayOK()
+ if !ok {
+ err := fmt.Errorf("response field 'values' is type array, but received BSON type %s", op.Result().Values.Type)
+
+ return &DistinctResult{err: err}
+ }
+
+ return &DistinctResult{
+ reg: coll.registry,
+ arr: bson.RawArray(arr),
+ bsonOpts: coll.bsonOpts,
+ }
+}
+
+// Find executes a find command and returns a Cursor over the matching documents in the collection.
+//
+// The filter parameter must be a document containing query operators and can be used to select which documents are
+// included in the result. It cannot be nil. An empty document (e.g. bson.D{}) should be used to include all documents.
+//
+// The opts parameter can be used to specify options for the operation (see the options.FindOptions documentation).
+//
+// For more information about the command, see https://www.mongodb.com/docs/manual/reference/command/find/.
+func (coll *Collection) Find(ctx context.Context, filter any,
+ opts ...options.Lister[options.FindOptions],
+) (*Cursor, error) {
+ args, err := mongoutil.NewOptions(opts...)
+ if err != nil {
+ return nil, err
+ }
+
+ // Omit "maxTimeMS" from operations that return a user-managed cursor to
+ // prevent confusing "cursor not found" errors.
+ //
+ // See DRIVERS-2722 for more detail.
+ return coll.find(ctx, filter, true, args)
+}
+
+func (coll *Collection) find(
+ ctx context.Context,
+ filter any,
+ omitMaxTimeMS bool,
+ args *options.FindOptions,
+) (cur *Cursor, err error) {
+ if ctx == nil {
+ ctx = context.Background()
+ }
+
+ f, err := marshal(filter, coll.bsonOpts, coll.registry)
+ if err != nil {
+ return nil, err
+ }
+
+ sess := sessionFromContext(ctx)
+ // Always close any created implicit sessions if Find returns an error.
+ defer func() {
+ if err != nil && sess != nil {
+ closeImplicitSession(sess)
+ }
+ }()
+ if sess == nil && coll.client.sessionPool != nil {
+ sess = session.NewImplicitClientSession(coll.client.sessionPool, coll.client.id)
+ }
+
+ err = coll.client.validSession(sess)
+ if err != nil {
+ return nil, err
+ }
+
+ rc := coll.readConcern
+ if sess.TransactionRunning() {
+ rc = nil
+ }
+
+ retry := driver.RetryNone
+ if coll.client.retryReads {
+ retry = driver.RetryOncePerCommand
+ }
+
+ cursorOpts := coll.client.createBaseCursorOptions(coll.client.retryReads)
+
+ selector := makeReadPrefSelector(sess, coll.readSelector, coll.client.localThreshold)
+ op := operation.NewFind(f).
+ Session(sess).ReadConcern(rc).ReadPreference(coll.readPreference).
+ CommandMonitor(coll.client.monitor).ServerSelector(selector).
+ Retry(retry).MaxAdaptiveRetries(cursorOpts.MaxAdaptiveRetries).
+ EnableOverloadRetargeting(cursorOpts.EnableOverloadRetargeting).
+ ClusterClock(coll.client.clock).Database(coll.db.name).Collection(coll.name).
+ Deployment(coll.client.deployment).Crypt(coll.client.cryptFLE).ServerAPI(coll.client.serverAPI).
+ Timeout(coll.client.timeout).Logger(coll.client.logger).Authenticator(coll.client.authenticator).
+ OmitMaxTimeMS(omitMaxTimeMS)
+
+ cursorOpts.MarshalValueEncoderFn = newEncoderFn(coll.bsonOpts, coll.registry)
+
+ if args.AllowDiskUse != nil {
+ op.AllowDiskUse(*args.AllowDiskUse)
+ }
+ if args.AllowPartialResults != nil {
+ op.AllowPartialResults(*args.AllowPartialResults)
+ }
+ if args.BatchSize != nil {
+ cursorOpts.BatchSize = *args.BatchSize
+ op.BatchSize(*args.BatchSize)
+ }
+ if args.Collation != nil {
+ op.Collation(bsoncore.Document(toDocument(args.Collation)))
+ }
+ if args.Comment != nil {
+ comment, err := marshalValue(args.Comment, coll.bsonOpts, coll.registry)
+ if err != nil {
+ return nil, err
+ }
+
+ op.Comment(comment)
+ cursorOpts.Comment = comment
+ }
+ if args.CursorType != nil {
+ switch *args.CursorType {
+ case options.Tailable:
+ op.Tailable(true)
+ case options.TailableAwait:
+ op.Tailable(true)
+ op.AwaitData(true)
+ }
+ }
+ if args.Hint != nil {
+ if isUnorderedMap(args.Hint) {
+ return nil, ErrMapForOrderedArgument{"hint"}
+ }
+ hint, err := marshalValue(args.Hint, coll.bsonOpts, coll.registry)
+ if err != nil {
+ return nil, err
+ }
+ op.Hint(hint)
+ }
+ if args.Let != nil {
+ let, err := marshal(args.Let, coll.bsonOpts, coll.registry)
+ if err != nil {
+ return nil, err
+ }
+ op.Let(let)
+ }
+ if args.Limit != nil {
+ limit := *args.Limit
+ if limit < 0 {
+ limit = -1 * limit
+ op.SingleBatch(true)
+ }
+ cursorOpts.Limit = int32(limit)
+ op.Limit(limit)
+ }
+ if args.Max != nil {
+ max, err := marshal(args.Max, coll.bsonOpts, coll.registry)
+ if err != nil {
+ return nil, err
+ }
+ op.Max(max)
+ }
+ if args.MaxAwaitTime != nil {
+ cursorOpts.SetMaxAwaitTime(*args.MaxAwaitTime)
+ }
+ if args.Min != nil {
+ min, err := marshal(args.Min, coll.bsonOpts, coll.registry)
+ if err != nil {
+ return nil, err
+ }
+ op.Min(min)
+ }
+ if args.NoCursorTimeout != nil {
+ op.NoCursorTimeout(*args.NoCursorTimeout)
+ }
+ if args.OplogReplay != nil {
+ op.OplogReplay(*args.OplogReplay)
+ }
+ if args.Projection != nil {
+ proj, err := marshal(args.Projection, coll.bsonOpts, coll.registry)
+ if err != nil {
+ return nil, err
+ }
+ op.Projection(proj)
+ }
+ if args.ReturnKey != nil {
+ op.ReturnKey(*args.ReturnKey)
+ }
+ if args.ShowRecordID != nil {
+ op.ShowRecordID(*args.ShowRecordID)
+ }
+ if args.Skip != nil {
+ op.Skip(*args.Skip)
+ }
+ if args.Sort != nil {
+ if isUnorderedMap(args.Sort) {
+ return nil, ErrMapForOrderedArgument{"sort"}
+ }
+ sort, err := marshal(args.Sort, coll.bsonOpts, coll.registry)
+ if err != nil {
+ return nil, err
+ }
+ op.Sort(sort)
+ }
+ if rawData, ok := optionsutil.Value(args.Internal, "rawData").(bool); ok {
+ op = op.RawData(rawData)
+ }
+
+ if err = op.Execute(ctx); err != nil {
+ return nil, wrapErrors(err)
+ }
+
+ bc, err := op.Result(cursorOpts)
+ if err != nil {
+ return nil, wrapErrors(err)
+ }
+
+ return newCursorWithSession(bc, coll.bsonOpts, coll.registry, sess,
+ withCursorOptionClientTimeout(coll.client.timeout))
+}
+
+func newFindArgsFromFindOneArgs(args *options.FindOneOptions) *options.FindOptions {
+ var limit int64 = -1
+ v := &options.FindOptions{Limit: &limit}
+ if args != nil {
+ v.AllowPartialResults = args.AllowPartialResults
+ v.Collation = args.Collation
+ v.Comment = args.Comment
+ v.Hint = args.Hint
+ v.Max = args.Max
+ v.Min = args.Min
+ v.OplogReplay = args.OplogReplay
+ v.Projection = args.Projection
+ v.ReturnKey = args.ReturnKey
+ v.ShowRecordID = args.ShowRecordID
+ v.Skip = args.Skip
+ v.Sort = args.Sort
+ v.Internal = args.Internal
+ }
+ return v
+}
+
+// FindOne executes a find command and returns a SingleResult for one document in the collection.
+//
+// The filter parameter must be a document containing query operators and can be used to select the document to be
+// returned. It cannot be nil. If the filter does not match any documents, a SingleResult with an error set to
+// ErrNoDocuments will be returned. If the filter matches multiple documents, one will be selected from the matched set.
+//
+// The opts parameter can be used to specify options for this operation (see the options.FindOneOptions documentation).
+//
+// For more information about the command, see https://www.mongodb.com/docs/manual/reference/command/find/.
+func (coll *Collection) FindOne(ctx context.Context, filter any,
+ opts ...options.Lister[options.FindOneOptions],
+) *SingleResult {
+ if ctx == nil {
+ ctx = context.Background()
+ }
+
+ args, err := mongoutil.NewOptions(opts...)
+ if err != nil {
+ return &SingleResult{err: err}
+ }
+ cursor, err := coll.find(ctx, filter, false, newFindArgsFromFindOneArgs(args))
+ return &SingleResult{
+ ctx: ctx,
+ cur: cursor,
+ bsonOpts: coll.bsonOpts,
+ reg: coll.registry,
+ err: wrapErrors(err),
+ }
+}
+
+func (coll *Collection) findAndModify(ctx context.Context, op *operation.FindAndModify) *SingleResult {
+ if ctx == nil {
+ ctx = context.Background()
+ }
+
+ sess := sessionFromContext(ctx)
+ var err error
+ if sess == nil && coll.client.sessionPool != nil {
+ sess = session.NewImplicitClientSession(coll.client.sessionPool, coll.client.id)
+ defer sess.EndSession()
+ }
+
+ err = coll.client.validSession(sess)
+ if err != nil {
+ return &SingleResult{err: err}
+ }
+
+ wc := coll.writeConcern
+ if sess.TransactionRunning() {
+ wc = nil
+ }
+ if !wc.Acknowledged() {
+ sess = nil
+ }
+
+ selector := makePinnedSelector(sess, coll.writeSelector)
+
+ retry := driver.RetryNone
+ if coll.client.retryWrites {
+ retry = driver.RetryOnce
+ }
+
+ maxAdaptiveRetries := coll.client.effectiveAdaptiveRetries(coll.client.retryWrites)
+
+ op = op.Session(sess).
+ WriteConcern(wc).
+ CommandMonitor(coll.client.monitor).
+ ServerSelector(selector).
+ ClusterClock(coll.client.clock).
+ Database(coll.db.name).
+ Collection(coll.name).
+ Deployment(coll.client.deployment).
+ Retry(retry).
+ MaxAdaptiveRetries(maxAdaptiveRetries).
+ EnableOverloadRetargeting(coll.client.enableOverloadRetargeting).
+ Crypt(coll.client.cryptFLE)
+
+ rr, err := processWriteError(op.Execute(ctx))
+ if err != nil {
+ return &SingleResult{err: err}
+ }
+
+ return &SingleResult{
+ ctx: ctx,
+ rdr: bson.Raw(op.Result().Value),
+ bsonOpts: coll.bsonOpts,
+ reg: coll.registry,
+ Acknowledged: rr.isAcknowledged(),
+ }
+}
+
+// FindOneAndDelete executes a findAndModify command to delete at most one document in the collection. and returns the
+// document as it appeared before deletion.
+//
+// The filter parameter must be a document containing query operators and can be used to select the document to be
+// deleted. It cannot be nil. If the filter does not match any documents, a SingleResult with an error set to
+// ErrNoDocuments wil be returned. If the filter matches multiple documents, one will be selected from the matched set.
+//
+// The opts parameter can be used to specify options for the operation (see the options.FindOneAndDeleteOptions
+// documentation).
+//
+// For more information about the command, see https://www.mongodb.com/docs/manual/reference/command/findAndModify/.
+func (coll *Collection) FindOneAndDelete(
+ ctx context.Context,
+ filter any,
+ opts ...options.Lister[options.FindOneAndDeleteOptions],
+) *SingleResult {
+ f, err := marshal(filter, coll.bsonOpts, coll.registry)
+ if err != nil {
+ return &SingleResult{err: err}
+ }
+
+ args, err := mongoutil.NewOptions[options.FindOneAndDeleteOptions](opts...)
+ if err != nil {
+ return &SingleResult{err: fmt.Errorf("failed to construct options from builder: %w", err)}
+ }
+
+ op := operation.NewFindAndModify(f).Remove(true).ServerAPI(coll.client.serverAPI).Timeout(coll.client.timeout).Authenticator(coll.client.authenticator)
+ if args.Collation != nil {
+ op = op.Collation(bsoncore.Document(toDocument(args.Collation)))
+ }
+ if args.Comment != nil {
+ comment, err := marshalValue(args.Comment, coll.bsonOpts, coll.registry)
+ if err != nil {
+ return &SingleResult{err: err}
+ }
+ op = op.Comment(comment)
+ }
+ if args.Projection != nil {
+ proj, err := marshal(args.Projection, coll.bsonOpts, coll.registry)
+ if err != nil {
+ return &SingleResult{err: err}
+ }
+ op = op.Fields(proj)
+ }
+ if args.Sort != nil {
+ if isUnorderedMap(args.Sort) {
+ return &SingleResult{err: ErrMapForOrderedArgument{"sort"}}
+ }
+ sort, err := marshal(args.Sort, coll.bsonOpts, coll.registry)
+ if err != nil {
+ return &SingleResult{err: err}
+ }
+ op = op.Sort(sort)
+ }
+ if args.Hint != nil {
+ if isUnorderedMap(args.Hint) {
+ return &SingleResult{err: ErrMapForOrderedArgument{"hint"}}
+ }
+ hint, err := marshalValue(args.Hint, coll.bsonOpts, coll.registry)
+ if err != nil {
+ return &SingleResult{err: err}
+ }
+ op = op.Hint(hint)
+ }
+ if args.Let != nil {
+ let, err := marshal(args.Let, coll.bsonOpts, coll.registry)
+ if err != nil {
+ return &SingleResult{err: err}
+ }
+ op = op.Let(let)
+ }
+ if rawData, ok := optionsutil.Value(args.Internal, "rawData").(bool); ok {
+ op = op.RawData(rawData)
+ }
+
+ return coll.findAndModify(ctx, op)
+}
+
+// FindOneAndReplace executes a findAndModify command to replace at most one document in the collection
+// and returns the document as it appeared before replacement.
+//
+// The filter parameter must be a document containing query operators and can be used to select the document to be
+// replaced. It cannot be nil. If the filter does not match any documents, a SingleResult with an error set to
+// ErrNoDocuments wil be returned. If the filter matches multiple documents, one will be selected from the matched set.
+//
+// The replacement parameter must be a document that will be used to replace the selected document. It cannot be nil
+// and cannot contain any update operators (https://www.mongodb.com/docs/manual/reference/operator/update/).
+//
+// The opts parameter can be used to specify options for the operation (see the options.FindOneAndReplaceOptions
+// documentation).
+//
+// For more information about the command, see https://www.mongodb.com/docs/manual/reference/command/findAndModify/.
+func (coll *Collection) FindOneAndReplace(
+ ctx context.Context,
+ filter any,
+ replacement any,
+ opts ...options.Lister[options.FindOneAndReplaceOptions],
+) *SingleResult {
+ f, err := marshal(filter, coll.bsonOpts, coll.registry)
+ if err != nil {
+ return &SingleResult{err: err}
+ }
+ r, err := marshal(replacement, coll.bsonOpts, coll.registry)
+ if err != nil {
+ return &SingleResult{err: err}
+ }
+ if firstElem, err := r.IndexErr(0); err == nil && strings.HasPrefix(firstElem.Key(), "$") {
+ return &SingleResult{err: errors.New("replacement document cannot contain keys beginning with '$'")}
+ }
+
+ args, err := mongoutil.NewOptions[options.FindOneAndReplaceOptions](opts...)
+ if err != nil {
+ return &SingleResult{err: fmt.Errorf("failed to construct options from builder: %w", err)}
+ }
+
+ op := operation.NewFindAndModify(f).Update(bsoncore.Value{Type: bsoncore.TypeEmbeddedDocument, Data: r}).
+ ServerAPI(coll.client.serverAPI).Timeout(coll.client.timeout).Authenticator(coll.client.authenticator)
+ if args.BypassDocumentValidation != nil && *args.BypassDocumentValidation {
+ op = op.BypassDocumentValidation(*args.BypassDocumentValidation)
+ }
+ if args.Collation != nil {
+ op = op.Collation(bsoncore.Document(toDocument(args.Collation)))
+ }
+ if args.Comment != nil {
+ comment, err := marshalValue(args.Comment, coll.bsonOpts, coll.registry)
+ if err != nil {
+ return &SingleResult{err: err}
+ }
+ op = op.Comment(comment)
+ }
+ if args.Projection != nil {
+ proj, err := marshal(args.Projection, coll.bsonOpts, coll.registry)
+ if err != nil {
+ return &SingleResult{err: err}
+ }
+ op = op.Fields(proj)
+ }
+ if args.ReturnDocument != nil {
+ op = op.NewDocument(*args.ReturnDocument == options.After)
+ }
+ if args.Sort != nil {
+ if isUnorderedMap(args.Sort) {
+ return &SingleResult{err: ErrMapForOrderedArgument{"sort"}}
+ }
+ sort, err := marshal(args.Sort, coll.bsonOpts, coll.registry)
+ if err != nil {
+ return &SingleResult{err: err}
+ }
+ op = op.Sort(sort)
+ }
+ if args.Upsert != nil {
+ op = op.Upsert(*args.Upsert)
+ }
+ if args.Hint != nil {
+ if isUnorderedMap(args.Hint) {
+ return &SingleResult{err: ErrMapForOrderedArgument{"hint"}}
+ }
+ hint, err := marshalValue(args.Hint, coll.bsonOpts, coll.registry)
+ if err != nil {
+ return &SingleResult{err: err}
+ }
+ op = op.Hint(hint)
+ }
+ if args.Let != nil {
+ let, err := marshal(args.Let, coll.bsonOpts, coll.registry)
+ if err != nil {
+ return &SingleResult{err: err}
+ }
+ op = op.Let(let)
+ }
+ if rawData, ok := optionsutil.Value(args.Internal, "rawData").(bool); ok {
+ op = op.RawData(rawData)
+ }
+ if additionalCmd, ok := optionsutil.Value(args.Internal, "addCommandFields").(bson.D); ok {
+ op = op.AdditionalCmd(additionalCmd)
+ }
+
+ return coll.findAndModify(ctx, op)
+}
+
+// FindOneAndUpdate executes a findAndModify command to update at most one document in the collection and returns the
+// document as it appeared before updating.
+//
+// The filter parameter must be a document containing query operators and can be used to select the document to be
+// updated. It cannot be nil. If the filter does not match any documents, a SingleResult with an error set to
+// ErrNoDocuments wil be returned. If the filter matches multiple documents, one will be selected from the matched set.
+//
+// The update parameter must be a document containing update operators
+// (https://www.mongodb.com/docs/manual/reference/operator/update/) and can be used to specify the modifications to be made
+// to the selected document. It cannot be nil or empty.
+//
+// The opts parameter can be used to specify options for the operation (see the options.FindOneAndUpdateOptions
+// documentation).
+//
+// For more information about the command, see https://www.mongodb.com/docs/manual/reference/command/findAndModify/.
+func (coll *Collection) FindOneAndUpdate(
+ ctx context.Context,
+ filter any,
+ update any,
+ opts ...options.Lister[options.FindOneAndUpdateOptions],
+) *SingleResult {
+ if ctx == nil {
+ ctx = context.Background()
+ }
+
+ f, err := marshal(filter, coll.bsonOpts, coll.registry)
+ if err != nil {
+ return &SingleResult{err: err}
+ }
+
+ args, err := mongoutil.NewOptions[options.FindOneAndUpdateOptions](opts...)
+ if err != nil {
+ return &SingleResult{err: fmt.Errorf("failed to construct options from builder: %w", err)}
+ }
+
+ op := operation.NewFindAndModify(f).ServerAPI(coll.client.serverAPI).Timeout(coll.client.timeout).Authenticator(coll.client.authenticator)
+
+ u, err := marshalUpdateValue(update, coll.bsonOpts, coll.registry, true)
+ if err != nil {
+ return &SingleResult{err: err}
+ }
+ op = op.Update(u)
+
+ if args.ArrayFilters != nil {
+ af := args.ArrayFilters
+ reg := coll.registry
+ filtersDoc, err := marshalValue(af, coll.bsonOpts, reg)
+ if err != nil {
+ return &SingleResult{err: err}
+ }
+ op = op.ArrayFilters(filtersDoc.Data)
+ }
+ if args.BypassDocumentValidation != nil && *args.BypassDocumentValidation {
+ op = op.BypassDocumentValidation(*args.BypassDocumentValidation)
+ }
+ if args.Collation != nil {
+ op = op.Collation(bsoncore.Document(toDocument(args.Collation)))
+ }
+ if args.Comment != nil {
+ comment, err := marshalValue(args.Comment, coll.bsonOpts, coll.registry)
+ if err != nil {
+ return &SingleResult{err: err}
+ }
+ op = op.Comment(comment)
+ }
+ if args.Projection != nil {
+ proj, err := marshal(args.Projection, coll.bsonOpts, coll.registry)
+ if err != nil {
+ return &SingleResult{err: err}
+ }
+ op = op.Fields(proj)
+ }
+ if args.ReturnDocument != nil {
+ op = op.NewDocument(*args.ReturnDocument == options.After)
+ }
+ if args.Sort != nil {
+ if isUnorderedMap(args.Sort) {
+ return &SingleResult{err: ErrMapForOrderedArgument{"sort"}}
+ }
+ sort, err := marshal(args.Sort, coll.bsonOpts, coll.registry)
+ if err != nil {
+ return &SingleResult{err: err}
+ }
+ op = op.Sort(sort)
+ }
+ if args.Upsert != nil {
+ op = op.Upsert(*args.Upsert)
+ }
+ if args.Hint != nil {
+ if isUnorderedMap(args.Hint) {
+ return &SingleResult{err: ErrMapForOrderedArgument{"hint"}}
+ }
+ hint, err := marshalValue(args.Hint, coll.bsonOpts, coll.registry)
+ if err != nil {
+ return &SingleResult{err: err}
+ }
+ op = op.Hint(hint)
+ }
+ if args.Let != nil {
+ let, err := marshal(args.Let, coll.bsonOpts, coll.registry)
+ if err != nil {
+ return &SingleResult{err: err}
+ }
+ op = op.Let(let)
+ }
+ if rawData, ok := optionsutil.Value(args.Internal, "rawData").(bool); ok {
+ op = op.RawData(rawData)
+ }
+ if additionalCmd, ok := optionsutil.Value(args.Internal, "addCommandFields").(bson.D); ok {
+ op = op.AdditionalCmd(additionalCmd)
+ }
+
+ return coll.findAndModify(ctx, op)
+}
+
+// Watch returns a change stream for all changes on the corresponding collection. See
+// https://www.mongodb.com/docs/manual/changeStreams/ for more information about change streams.
+//
+// The Collection must be configured with read concern majority or no read concern for a change stream to be created
+// successfully.
+//
+// The pipeline parameter must be an array of documents, each representing a pipeline stage. The pipeline cannot be
+// nil but can be empty. The stage documents must all be non-nil. See https://www.mongodb.com/docs/manual/changeStreams/ for
+// a list of pipeline stages that can be used with change streams. For a pipeline of bson.D documents, the
+// mongo.Pipeline{} type can be used.
+//
+// The opts parameter can be used to specify options for change stream creation (see the options.ChangeStreamOptions
+// documentation).
+func (coll *Collection) Watch(ctx context.Context, pipeline any,
+ opts ...options.Lister[options.ChangeStreamOptions],
+) (*ChangeStream, error) {
+ csConfig := changeStreamConfig{
+ readConcern: coll.readConcern,
+ readPreference: coll.readPreference,
+ client: coll.client,
+ bsonOpts: coll.bsonOpts,
+ registry: coll.registry,
+ streamType: CollectionStream,
+ collectionName: coll.Name(),
+ databaseName: coll.db.Name(),
+ crypt: coll.client.cryptFLE,
+ }
+ return newChangeStream(ctx, csConfig, pipeline, opts...)
+}
+
+// Indexes returns an IndexView instance that can be used to perform operations on the indexes for the collection.
+func (coll *Collection) Indexes() IndexView {
+ return IndexView{coll: coll}
+}
+
+// SearchIndexes returns a SearchIndexView instance that can be used to perform operations on the search indexes for the collection.
+func (coll *Collection) SearchIndexes() SearchIndexView {
+ c := coll.Clone()
+ c.readConcern = nil
+ c.writeConcern = nil
+ return SearchIndexView{
+ coll: c,
+ }
+}
+
+// Drop drops the collection on the server. This method ignores "namespace not found" errors so it is safe to drop
+// a collection that does not exist on the server.
+func (coll *Collection) Drop(ctx context.Context, opts ...options.Lister[options.DropCollectionOptions]) error {
+ args, err := mongoutil.NewOptions[options.DropCollectionOptions](opts...)
+ if err != nil {
+ return fmt.Errorf("failed to construct options from builder: %w", err)
+ }
+
+ ef := args.EncryptedFields
+
+ if ef == nil {
+ ef = coll.db.getEncryptedFieldsFromMap(coll.name)
+ }
+
+ if ef == nil && coll.db.client.encryptedFieldsMap != nil {
+ var err error
+ if ef, err = coll.db.getEncryptedFieldsFromServer(ctx, coll.name); err != nil {
+ return err
+ }
+ }
+
+ if ef != nil {
+ return coll.dropEncryptedCollection(ctx, ef)
+ }
+
+ return coll.drop(ctx)
+}
+
+// dropEncryptedCollection drops a collection with EncryptedFields.
+func (coll *Collection) dropEncryptedCollection(ctx context.Context, ef any) error {
+ efBSON, err := marshal(ef, coll.bsonOpts, coll.registry)
+ if err != nil {
+ return fmt.Errorf("error transforming document: %w", err)
+ }
+
+ // Drop the two encryption-related, associated collections: `escCollection` and `ecocCollection`.
+ // Drop ESCCollection.
+ escCollection, err := csfle.GetEncryptedStateCollectionName(efBSON, coll.name, csfle.EncryptedStateCollection)
+ if err != nil {
+ return err
+ }
+ if err := coll.db.Collection(escCollection).drop(ctx); err != nil {
+ return err
+ }
+
+ // Drop ECOCCollection.
+ ecocCollection, err := csfle.GetEncryptedStateCollectionName(efBSON, coll.name, csfle.EncryptedCompactionCollection)
+ if err != nil {
+ return err
+ }
+ if err := coll.db.Collection(ecocCollection).drop(ctx); err != nil {
+ return err
+ }
+
+ // Drop the data collection.
+ return coll.drop(ctx)
+}
+
+// drop drops a collection without EncryptedFields.
+func (coll *Collection) drop(ctx context.Context) error {
+ if ctx == nil {
+ ctx = context.Background()
+ }
+
+ sess := sessionFromContext(ctx)
+ if sess == nil && coll.client.sessionPool != nil {
+ sess = session.NewImplicitClientSession(coll.client.sessionPool, coll.client.id)
+ defer sess.EndSession()
+ }
+
+ err := coll.client.validSession(sess)
+ if err != nil {
+ return err
+ }
+
+ wc := coll.writeConcern
+ if sess.TransactionRunning() {
+ wc = nil
+ }
+ if !wc.Acknowledged() {
+ sess = nil
+ }
+
+ selector := makePinnedSelector(sess, coll.writeSelector)
+
+ op := operation.NewDropCollection().
+ Session(sess).WriteConcern(wc).CommandMonitor(coll.client.monitor).
+ ServerSelector(selector).ClusterClock(coll.client.clock).
+ Database(coll.db.name).Collection(coll.name).
+ Deployment(coll.client.deployment).Crypt(coll.client.cryptFLE).
+ ServerAPI(coll.client.serverAPI).Timeout(coll.client.timeout).
+ Authenticator(coll.client.authenticator)
+ err = op.Execute(ctx)
+
+ // ignore namespace not found errors
+ var driverErr driver.Error
+ if !errors.As(err, &driverErr) || !driverErr.NamespaceNotFound() {
+ return wrapErrors(err)
+ }
+ return nil
+}
+
+func toDocument(co *options.Collation) bson.Raw {
+ idx, doc := bsoncore.AppendDocumentStart(nil)
+ if co.Locale != "" {
+ doc = bsoncore.AppendStringElement(doc, "locale", co.Locale)
+ }
+ if co.CaseLevel {
+ doc = bsoncore.AppendBooleanElement(doc, "caseLevel", true)
+ }
+ if co.CaseFirst != "" {
+ doc = bsoncore.AppendStringElement(doc, "caseFirst", co.CaseFirst)
+ }
+ if co.Strength != 0 {
+ doc = bsoncore.AppendInt32Element(doc, "strength", int32(co.Strength))
+ }
+ if co.NumericOrdering {
+ doc = bsoncore.AppendBooleanElement(doc, "numericOrdering", true)
+ }
+ if co.Alternate != "" {
+ doc = bsoncore.AppendStringElement(doc, "alternate", co.Alternate)
+ }
+ if co.MaxVariable != "" {
+ doc = bsoncore.AppendStringElement(doc, "maxVariable", co.MaxVariable)
+ }
+ if co.Normalization {
+ doc = bsoncore.AppendBooleanElement(doc, "normalization", true)
+ }
+ if co.Backwards {
+ doc = bsoncore.AppendBooleanElement(doc, "backwards", true)
+ }
+ doc, _ = bsoncore.AppendDocumentEnd(doc, idx)
+ return doc
+}
+
+type pinnedServerSelector struct {
+ stringer fmt.Stringer
+ fallback description.ServerSelector
+ session *session.Client
+}
+
+var _ description.ServerSelector = pinnedServerSelector{}
+
+func (pss pinnedServerSelector) String() string {
+ if pss.stringer == nil {
+ return ""
+ }
+
+ return pss.stringer.String()
+}
+
+func (pss pinnedServerSelector) SelectServer(
+ t description.Topology,
+ svrs []description.Server,
+) ([]description.Server, error) {
+ if pss.session != nil && pss.session.PinnedServerAddr != nil {
+ // If there is a pinned server, try to find it in the list of candidates.
+ for _, candidate := range svrs {
+ if candidate.Addr == *pss.session.PinnedServerAddr {
+ return []description.Server{candidate}, nil
+ }
+ }
+
+ return nil, nil
+ }
+
+ return pss.fallback.SelectServer(t, svrs)
+}
+
+func makePinnedSelector(sess *session.Client, fallback description.ServerSelector) pinnedServerSelector {
+ pss := pinnedServerSelector{
+ session: sess,
+ fallback: fallback,
+ }
+
+ if srvSelectorStringer, ok := fallback.(fmt.Stringer); ok {
+ pss.stringer = srvSelectorStringer
+ }
+
+ return pss
+}
+
+func makeReadPrefSelector(
+ sess *session.Client,
+ selector description.ServerSelector,
+ localThreshold time.Duration,
+) pinnedServerSelector {
+ if sess != nil && sess.TransactionRunning() {
+ selector = &serverselector.Composite{
+ Selectors: []description.ServerSelector{
+ &serverselector.ReadPref{ReadPref: sess.CurrentRp},
+ &serverselector.Latency{Latency: localThreshold},
+ },
+ }
+ }
+
+ return makePinnedSelector(sess, selector)
+}
+
+func makeOutputAggregateSelector(
+ sess *session.Client,
+ rp *readpref.ReadPref,
+ localThreshold time.Duration,
+) pinnedServerSelector {
+ if sess != nil && sess.TransactionRunning() {
+ // Use current transaction's read preference if available
+ rp = sess.CurrentRp
+ }
+
+ selector := &serverselector.Composite{
+ Selectors: []description.ServerSelector{
+ &serverselector.ReadPref{ReadPref: rp, IsOutputAggregate: true},
+ &serverselector.Latency{Latency: localThreshold},
+ },
+ }
+
+ return makePinnedSelector(sess, selector)
+}
+
+// isUnorderedMap returns true if val is a map with more than 1 element. It is typically used to
+// check for unordered Go values that are used in nested command documents where different field
+// orders mean different things. Examples are the "sort" and "hint" fields.
+func isUnorderedMap(val any) bool {
+ refValue := reflect.ValueOf(val)
+ return refValue.Kind() == reflect.Map && refValue.Len() > 1
+}
diff --git a/vendor/go.mongodb.org/mongo-driver/v2/mongo/crypt_retrievers.go b/vendor/go.mongodb.org/mongo-driver/v2/mongo/crypt_retrievers.go
new file mode 100644
index 0000000..2354328
--- /dev/null
+++ b/vendor/go.mongodb.org/mongo-driver/v2/mongo/crypt_retrievers.go
@@ -0,0 +1,65 @@
+// Copyright (C) MongoDB, Inc. 2017-present.
+//
+// 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
+
+package mongo
+
+import (
+ "context"
+
+ "go.mongodb.org/mongo-driver/v2/x/bsonx/bsoncore"
+)
+
+// keyRetriever gets keys from the key vault collection.
+type keyRetriever struct {
+ coll *Collection
+}
+
+func (kr *keyRetriever) cryptKeys(ctx context.Context, filter bsoncore.Document) ([]bsoncore.Document, error) {
+ // Remove the explicit session from the context if one is set.
+ // The explicit session may be from a different client.
+ ctx = NewSessionContext(ctx, nil)
+ cursor, err := kr.coll.Find(ctx, filter)
+ if err != nil {
+ return nil, EncryptionKeyVaultError{Wrapped: err}
+ }
+ defer cursor.Close(ctx)
+
+ var results []bsoncore.Document
+ for cursor.Next(ctx) {
+ cur := make([]byte, len(cursor.Current))
+ copy(cur, cursor.Current)
+ results = append(results, cur)
+ }
+ if err = cursor.Err(); err != nil {
+ return nil, EncryptionKeyVaultError{Wrapped: err}
+ }
+
+ return results, nil
+}
+
+// collInfoRetriever gets info for collections from a database.
+type collInfoRetriever struct {
+ client *Client
+}
+
+func (cir *collInfoRetriever) cryptCollInfo(ctx context.Context, db string, filter bsoncore.Document) (bsoncore.Document, error) {
+ // Remove the explicit session from the context if one is set.
+ // The explicit session may be from a different client.
+ ctx = NewSessionContext(ctx, nil)
+ cursor, err := cir.client.Database(db).ListCollections(ctx, filter)
+ if err != nil {
+ return nil, err
+ }
+ defer cursor.Close(ctx)
+
+ if !cursor.Next(ctx) {
+ return nil, cursor.Err()
+ }
+
+ res := make([]byte, len(cursor.Current))
+ copy(res, cursor.Current)
+ return res, nil
+}
diff --git a/vendor/go.mongodb.org/mongo-driver/v2/mongo/cursor.go b/vendor/go.mongodb.org/mongo-driver/v2/mongo/cursor.go
new file mode 100644
index 0000000..f175657
--- /dev/null
+++ b/vendor/go.mongodb.org/mongo-driver/v2/mongo/cursor.go
@@ -0,0 +1,495 @@
+// Copyright (C) MongoDB, Inc. 2017-present.
+//
+// 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
+
+package mongo
+
+import (
+ "bytes"
+ "context"
+ "errors"
+ "fmt"
+ "io"
+ "reflect"
+ "time"
+
+ "go.mongodb.org/mongo-driver/v2/bson"
+ "go.mongodb.org/mongo-driver/v2/internal/mongoutil"
+ "go.mongodb.org/mongo-driver/v2/mongo/options"
+ "go.mongodb.org/mongo-driver/v2/x/bsonx/bsoncore"
+ "go.mongodb.org/mongo-driver/v2/x/mongo/driver"
+ "go.mongodb.org/mongo-driver/v2/x/mongo/driver/session"
+)
+
+// Cursor is used to iterate over a stream of documents. Each document can be decoded into a Go type via the Decode
+// method or accessed as raw BSON via the Current field. This type is not goroutine safe and must not be used
+// concurrently by multiple goroutines.
+type Cursor struct {
+ // Current contains the BSON bytes of the current change document. This property is only valid until the next call
+ // to Next or TryNext. If continued access is required, a copy must be made.
+ Current bson.Raw
+
+ bc batchCursor
+ batch *bsoncore.Iterator
+ batchLength int
+ bsonOpts *options.BSONOptions
+ registry *bson.Registry
+ clientSession *session.Client
+ clientTimeout time.Duration
+ hasClientTimeout bool
+
+ err error
+}
+
+type cursorOptions struct {
+ clientTimeout time.Duration
+ hasClientTimeout bool
+}
+
+type cursorOption func(*cursorOptions)
+
+func withCursorOptionClientTimeout(dur *time.Duration) cursorOption {
+ return func(opts *cursorOptions) {
+ if dur != nil && *dur > 0 {
+ opts.clientTimeout = *dur
+ opts.hasClientTimeout = true
+ }
+ }
+}
+
+func newCursor(
+ bc batchCursor,
+ bsonOpts *options.BSONOptions,
+ registry *bson.Registry,
+ opts ...cursorOption,
+) (*Cursor, error) {
+ return newCursorWithSession(bc, bsonOpts, registry, nil, opts...)
+}
+
+func newCursorWithSession(
+ bc batchCursor,
+ bsonOpts *options.BSONOptions,
+ registry *bson.Registry,
+ clientSession *session.Client,
+ opts ...cursorOption,
+) (*Cursor, error) {
+ if registry == nil {
+ registry = defaultRegistry
+ }
+ if bc == nil {
+ return nil, errors.New("batch cursor must not be nil")
+ }
+
+ cursorOpts := &cursorOptions{}
+ for _, opt := range opts {
+ opt(cursorOpts)
+ }
+
+ c := &Cursor{
+ bc: bc,
+ bsonOpts: bsonOpts,
+ registry: registry,
+ clientSession: clientSession,
+ clientTimeout: cursorOpts.clientTimeout,
+ hasClientTimeout: cursorOpts.hasClientTimeout,
+ }
+ if bc.ID() == 0 {
+ c.closeImplicitSession()
+ }
+
+ // Initialize just the batchLength here so RemainingBatchLength will return an
+ // accurate result. The actual batch will be pulled up by the first
+ // Next/TryNext call.
+ c.batchLength = c.bc.Batch().Count()
+ return c, nil
+}
+
+func newEmptyCursor() *Cursor {
+ return &Cursor{bc: driver.NewEmptyBatchCursor()}
+}
+
+// NewCursorFromDocuments creates a new Cursor pre-loaded with the provided documents, error and registry. If no registry is provided,
+// bson.NewRegistry() will be used.
+//
+// The documents parameter must be a slice of documents. The slice may be nil or empty, but all elements must be non-nil.
+func NewCursorFromDocuments(documents []any, preloadedErr error, registry *bson.Registry) (*Cursor, error) {
+ if registry == nil {
+ registry = defaultRegistry
+ }
+
+ buf := new(bytes.Buffer)
+ enc := new(bson.Encoder)
+
+ values := make([]bsoncore.Value, len(documents))
+ for i, doc := range documents {
+ switch t := doc.(type) {
+ case nil:
+ return nil, fmt.Errorf("invalid document at index %d: %w", i, ErrNilDocument)
+ case []byte:
+ // Slight optimization so we'll just use MarshalBSON and not go through the codec machinery.
+ doc = bson.Raw(t)
+ }
+
+ vw := bson.NewDocumentWriter(buf)
+ enc.Reset(vw)
+ enc.SetRegistry(registry)
+
+ if err := enc.Encode(doc); err != nil {
+ return nil, err
+ }
+
+ dup := make([]byte, len(buf.Bytes()))
+ copy(dup, buf.Bytes())
+
+ values[i] = bsoncore.Value{
+ Type: bsoncore.TypeEmbeddedDocument,
+ Data: dup,
+ }
+
+ buf.Reset()
+ }
+
+ c := &Cursor{
+ bc: driver.NewBatchCursorFromList(bsoncore.BuildArray(nil, values...)),
+ registry: registry,
+ err: preloadedErr,
+ }
+
+ // Initialize batch and batchLength here. The underlying batch cursor will be preloaded with the
+ // provided contents, and thus already has a batch before calls to Next/TryNext.
+ c.batch = c.bc.Batch()
+ c.batchLength = c.bc.Batch().Count()
+
+ return c, nil
+}
+
+// ID returns the ID of this cursor, or 0 if the cursor has been closed or exhausted.
+func (c *Cursor) ID() int64 { return c.bc.ID() }
+
+// Next gets the next document for this cursor. It returns true if there were no
+// errors and the cursor has not been exhausted.
+//
+// Next blocks until a document is available or an error occurs. If the context
+// expires, the cursor's error will be set to ctx.Err(). In case of an error,
+// Next will return false.
+//
+// If MaxAwaitTime is set, the operation will be bound by the Context's
+// deadline. If the context does not have a deadline, the operation will be
+// bound by the client-level timeout, if one is set. If MaxAwaitTime is greater
+// than the user-provided timeout, Next will return false.
+//
+// If Next returns false, subsequent calls will also return false.
+func (c *Cursor) Next(ctx context.Context) bool {
+ return c.next(ctx, false)
+}
+
+// TryNext attempts to get the next document for this cursor. It returns true if there were no errors and the next
+// document is available. This is only recommended for use with tailable cursors as a non-blocking alternative to
+// Next. See https://www.mongodb.com/docs/manual/core/tailable-cursors/ for more information about tailable cursors.
+//
+// TryNext returns false if the cursor is exhausted, an error occurs when getting results from the server, the next
+// document is not yet available, or ctx expires. If the context expires, the cursor's error will be set to ctx.Err().
+//
+// If TryNext returns false and an error occurred or the cursor has been exhausted (i.e. c.Err() != nil || c.ID() == 0),
+// subsequent attempts will also return false. Otherwise, it is safe to call TryNext again until a document is
+// available.
+//
+// This method requires driver version >= 1.2.0.
+func (c *Cursor) TryNext(ctx context.Context) bool {
+ return c.next(ctx, true)
+}
+
+func (c *Cursor) next(ctx context.Context, nonBlocking bool) bool {
+ // return false right away if the cursor has already errored.
+ if c.err != nil {
+ return false
+ }
+
+ if ctx == nil {
+ ctx = context.Background()
+ }
+
+ // If the context does not have a deadline we defer to a client-level timeout,
+ // if one is set.
+ if _, ok := ctx.Deadline(); !ok && c.hasClientTimeout {
+ var cancel context.CancelFunc
+ ctx, cancel = context.WithTimeout(ctx, c.clientTimeout)
+
+ defer cancel()
+ }
+
+ // To avoid unnecessary socket timeouts, we attempt to short-circuit tailable
+ // awaitData "getMore" operations by ensuring that the maxAwaitTimeMS is less
+ // than the operation timeout.
+ //
+ // The specifications assume that drivers iteratively apply the timeout
+ // provided at the constructor level (e.g., (*collection).Find) for tailable
+ // awaitData cursors:
+ //
+ // If set, drivers MUST apply the timeoutMS option to the initial aggregate
+ // operation. Drivers MUST also apply the original timeoutMS value to each
+ // next call on the change stream but MUST NOT use it to derive a maxTimeMS
+ // field for getMore commands.
+ //
+ // The Go Driver might decide to support the above behavior with DRIVERS-2722.
+ // The principal concern is that it would be unexpected for users to apply an
+ // operation-level timeout via contexts to a constructor and then that timeout
+ // later be applied while working with a resulting cursor. Instead, it is more
+ // idiomatic to apply the timeout to the context passed to Next or TryNext.
+ maxAwaitTime := c.bc.MaxAwaitTime() //
+ if maxAwaitTime != nil && !nonBlocking && !mongoutil.TimeoutWithinContext(ctx, *maxAwaitTime) {
+ c.err = fmt.Errorf("MaxAwaitTime must be less than the operation timeout")
+
+ return false
+ }
+
+ val, err := c.batch.Next()
+ switch {
+ case err == nil:
+ // Consume the next document in the current batch.
+ c.batchLength--
+ c.Current = bson.Raw(val.Data)
+ return true
+ case errors.Is(err, io.EOF): // Need to do a getMore
+ default:
+ c.err = err
+ return false
+ }
+
+ // call the Next method in a loop until at least one document is returned in the next batch or
+ // the context times out.
+ for {
+ // If we don't have a next batch
+ if !c.bc.Next(ctx) {
+ // Do we have an error? If so we return false.
+ c.err = wrapErrors(c.bc.Err())
+ if c.err != nil {
+ return false
+ }
+ // Is the cursor ID zero?
+ if c.bc.ID() == 0 {
+ c.closeImplicitSession()
+ return false
+ }
+ // empty batch, but cursor is still valid.
+ // use nonBlocking to determine if we should continue or return control to the caller.
+ if nonBlocking {
+ return false
+ }
+ continue
+ }
+
+ // close the implicit session if this was the last getMore
+ if c.bc.ID() == 0 {
+ c.closeImplicitSession()
+ }
+
+ // Use the new batch to update the batch and batchLength fields. Consume the first document in the batch.
+ c.batch = c.bc.Batch()
+ c.batchLength = c.batch.Count()
+ val, err = c.batch.Next()
+ switch {
+ case err == nil:
+ c.batchLength--
+ c.Current = bson.Raw(val.Data)
+ return true
+ case errors.Is(err, io.EOF): // Empty batch so we continue
+ default:
+ c.err = err
+ return false
+ }
+ }
+}
+
+func getDecoder(
+ data []byte,
+ opts *options.BSONOptions,
+ reg *bson.Registry,
+) *bson.Decoder {
+ dec := bson.NewDecoder(bson.NewDocumentReader(bytes.NewReader(data)))
+
+ if opts != nil {
+ if opts.AllowTruncatingDoubles {
+ dec.AllowTruncatingDoubles()
+ }
+ if opts.BinaryAsSlice {
+ dec.BinaryAsSlice()
+ }
+ if opts.DefaultDocumentM {
+ dec.DefaultDocumentM()
+ }
+ if opts.DefaultDocumentMap {
+ dec.DefaultDocumentMap()
+ }
+ if opts.ObjectIDAsHexString {
+ dec.ObjectIDAsHexString()
+ }
+ if opts.UseJSONStructTags {
+ dec.UseJSONStructTags()
+ }
+ if opts.UseLocalTimeZone {
+ dec.UseLocalTimeZone()
+ }
+ if opts.ZeroMaps {
+ dec.ZeroMaps()
+ }
+ if opts.ZeroStructs {
+ dec.ZeroStructs()
+ }
+ }
+
+ if reg != nil {
+ dec.SetRegistry(reg)
+ }
+
+ return dec
+}
+
+// Decode will unmarshal the current document into val and return any errors from the unmarshalling process without any
+// modification. If val is nil or is a typed nil, an error will be returned.
+func (c *Cursor) Decode(val any) error {
+ dec := getDecoder(c.Current, c.bsonOpts, c.registry)
+
+ return dec.Decode(val)
+}
+
+// Err returns the last error seen by the Cursor, or nil if no error has occurred.
+func (c *Cursor) Err() error { return c.err }
+
+// Close closes this cursor. Next and TryNext must not be called after Close has been called. Close is idempotent. After
+// the first call, any subsequent calls will not change the state.
+func (c *Cursor) Close(ctx context.Context) error {
+ defer c.closeImplicitSession()
+ return wrapErrors(c.bc.Close(ctx))
+}
+
+// All iterates the cursor and decodes each document into results. The results parameter must be a pointer to a slice.
+// The slice pointed to by results will be completely overwritten. A nil slice pointer will not be modified if the cursor
+// has been closed, exhausted, or is empty. This method will close the cursor after retrieving all documents. If the
+// cursor has been iterated, any previously iterated documents will not be included in results.
+//
+// This method requires driver version >= 1.1.0.
+func (c *Cursor) All(ctx context.Context, results any) error {
+ resultsVal := reflect.ValueOf(results)
+ if resultsVal.Kind() != reflect.Ptr {
+ return fmt.Errorf("results argument must be a pointer to a slice, but was a %s", resultsVal.Kind())
+ }
+
+ sliceVal := resultsVal.Elem()
+ if sliceVal.Kind() == reflect.Interface {
+ sliceVal = sliceVal.Elem()
+ }
+
+ if sliceVal.Kind() != reflect.Slice {
+ return fmt.Errorf("results argument must be a pointer to a slice, but was a pointer to %s", sliceVal.Kind())
+ }
+
+ elementType := sliceVal.Type().Elem()
+ var index int
+ var err error
+
+ // Defer a call to Close to try to clean up the cursor server-side when all
+ // documents have not been exhausted. Use context.Background() to ensure Close
+ // completes even if the context passed to All has errored.
+ defer c.Close(context.Background())
+
+ batch := c.batch // exhaust the current batch before iterating the batch cursor
+ for {
+ sliceVal, index, err = c.addFromBatch(sliceVal, elementType, batch, index)
+ if err != nil {
+ return err
+ }
+
+ if !c.bc.Next(ctx) {
+ break
+ }
+
+ batch = c.bc.Batch()
+ }
+
+ if err = wrapErrors(c.bc.Err()); err != nil {
+ return err
+ }
+
+ resultsVal.Elem().Set(sliceVal.Slice(0, index))
+ return nil
+}
+
+// RemainingBatchLength returns the number of documents left in the current batch. If this returns zero, the subsequent
+// call to Next or TryNext will do a network request to fetch the next batch.
+func (c *Cursor) RemainingBatchLength() int {
+ return c.batchLength
+}
+
+// addFromBatch adds all documents from batch to sliceVal starting at the given index. It returns the new slice value,
+// the next empty index in the slice, and an error if one occurs.
+func (c *Cursor) addFromBatch(sliceVal reflect.Value, elemType reflect.Type, batch *bsoncore.Iterator,
+ index int,
+) (reflect.Value, int, error) {
+ docs, err := batch.Documents()
+ if err != nil {
+ return sliceVal, index, err
+ }
+
+ for _, doc := range docs {
+ if sliceVal.Len() == index {
+ // slice is full
+ newElem := reflect.New(elemType)
+ sliceVal = reflect.Append(sliceVal, newElem.Elem())
+ sliceVal = sliceVal.Slice(0, sliceVal.Cap())
+ }
+
+ currElem := sliceVal.Index(index).Addr().Interface()
+ dec := getDecoder(doc, c.bsonOpts, c.registry)
+ err = dec.Decode(currElem)
+ if err != nil {
+ return sliceVal, index, err
+ }
+
+ index++
+ }
+
+ return sliceVal, index, nil
+}
+
+func (c *Cursor) closeImplicitSession() {
+ if c.clientSession != nil && c.clientSession.IsImplicit {
+ c.clientSession.EndSession()
+ }
+}
+
+// SetBatchSize sets the number of documents to fetch from the database with
+// each iteration of the cursor's "Next" method. Note that some operations set
+// an initial cursor batch size, so this setting only affects subsequent
+// document batches fetched from the database.
+func (c *Cursor) SetBatchSize(batchSize int32) {
+ c.bc.SetBatchSize(batchSize)
+}
+
+// SetMaxAwaitTime will set the maximum amount of time the server will allow the
+// operations to execute. The server will error if this field is set but the
+// cursor is not configured with awaitData=true.
+//
+// The time.Duration value passed by this setter will be converted and rounded
+// down to the nearest millisecond.
+func (c *Cursor) SetMaxAwaitTime(dur time.Duration) {
+ c.bc.SetMaxAwaitTime(dur)
+}
+
+// SetComment will set a user-configurable comment that can be used to identify
+// the operation in server logs.
+func (c *Cursor) SetComment(comment any) {
+ c.bc.SetComment(comment)
+}
+
+// BatchCursorFromCursor returns a driver.BatchCursor for the given Cursor. If there is no underlying
+// driver.BatchCursor, nil is returned.
+//
+// Deprecated: This is an unstable function because the driver.BatchCursor type exists in the "x" package. Neither this
+// function nor the driver.BatchCursor type should be used by applications and may be changed or removed in any release.
+func BatchCursorFromCursor(c *Cursor) *driver.BatchCursor {
+ bc, _ := c.bc.(*driver.BatchCursor)
+ return bc
+}
diff --git a/vendor/go.mongodb.org/mongo-driver/v2/mongo/database.go b/vendor/go.mongodb.org/mongo-driver/v2/mongo/database.go
new file mode 100644
index 0000000..c1d3474
--- /dev/null
+++ b/vendor/go.mongodb.org/mongo-driver/v2/mongo/database.go
@@ -0,0 +1,991 @@
+// Copyright (C) MongoDB, Inc. 2017-present.
+//
+// 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
+
+package mongo
+
+import (
+ "context"
+ "errors"
+ "fmt"
+ "time"
+
+ "go.mongodb.org/mongo-driver/v2/bson"
+ "go.mongodb.org/mongo-driver/v2/internal/csfle"
+ "go.mongodb.org/mongo-driver/v2/internal/csot"
+ "go.mongodb.org/mongo-driver/v2/internal/mongoutil"
+ "go.mongodb.org/mongo-driver/v2/internal/optionsutil"
+ "go.mongodb.org/mongo-driver/v2/internal/serverselector"
+ "go.mongodb.org/mongo-driver/v2/mongo/options"
+ "go.mongodb.org/mongo-driver/v2/mongo/readconcern"
+ "go.mongodb.org/mongo-driver/v2/mongo/readpref"
+ "go.mongodb.org/mongo-driver/v2/mongo/writeconcern"
+ "go.mongodb.org/mongo-driver/v2/x/bsonx/bsoncore"
+ "go.mongodb.org/mongo-driver/v2/x/mongo/driver"
+ "go.mongodb.org/mongo-driver/v2/x/mongo/driver/description"
+ "go.mongodb.org/mongo-driver/v2/x/mongo/driver/operation"
+ "go.mongodb.org/mongo-driver/v2/x/mongo/driver/session"
+)
+
+var defaultRunCmdOpts = []options.Lister[options.RunCmdOptions]{options.RunCmd().SetReadPreference(readpref.Primary())}
+
+// Database is a handle to a MongoDB database. It is safe for concurrent use by multiple goroutines.
+type Database struct {
+ client *Client
+ name string
+ readConcern *readconcern.ReadConcern
+ writeConcern *writeconcern.WriteConcern
+ readPreference *readpref.ReadPref
+ readSelector description.ServerSelector
+ writeSelector description.ServerSelector
+ bsonOpts *options.BSONOptions
+ registry *bson.Registry
+}
+
+func newDatabase(client *Client, name string, opts ...options.Lister[options.DatabaseOptions]) *Database {
+ args, _ := mongoutil.NewOptions[options.DatabaseOptions](opts...)
+
+ rc := client.readConcern
+ if args.ReadConcern != nil {
+ rc = args.ReadConcern
+ }
+
+ rp := client.readPreference
+ if args.ReadPreference != nil {
+ rp = args.ReadPreference
+ }
+
+ wc := client.writeConcern
+ if args.WriteConcern != nil {
+ wc = args.WriteConcern
+ }
+
+ bsonOpts := client.bsonOpts
+ if args.BSONOptions != nil {
+ bsonOpts = args.BSONOptions
+ }
+
+ reg := client.registry
+ if args.Registry != nil {
+ reg = args.Registry
+ }
+
+ db := &Database{
+ client: client,
+ name: name,
+ readPreference: rp,
+ readConcern: rc,
+ writeConcern: wc,
+ bsonOpts: bsonOpts,
+ registry: reg,
+ }
+
+ db.readSelector = &serverselector.Composite{
+ Selectors: []description.ServerSelector{
+ &serverselector.ReadPref{ReadPref: db.readPreference},
+ &serverselector.Latency{Latency: db.client.localThreshold},
+ },
+ }
+
+ db.writeSelector = &serverselector.Composite{
+ Selectors: []description.ServerSelector{
+ &serverselector.Write{},
+ &serverselector.Latency{Latency: db.client.localThreshold},
+ },
+ }
+
+ return db
+}
+
+// Client returns the Client the Database was created from.
+func (db *Database) Client() *Client {
+ return db.client
+}
+
+// Name returns the name of the database.
+func (db *Database) Name() string {
+ return db.name
+}
+
+// Collection returns a handle for a collection with the given name and options.
+//
+// If the collection does not exist on the server, it will be created when a
+// write operation is performed.
+func (db *Database) Collection(name string, opts ...options.Lister[options.CollectionOptions]) *Collection {
+ return newCollection(db, name, opts...)
+}
+
+// Aggregate executes an aggregate command the database.
+//
+// The pipeline parameter must be a slice of documents, each representing an aggregation stage. The pipeline
+// cannot be nil but can be empty. The stage documents must all be non-nil. For a pipeline of bson.D documents, the
+// mongo.Pipeline type can be used. See
+// https://www.mongodb.com/docs/manual/reference/operator/aggregation-pipeline/#db-aggregate-stages for a list of valid
+// stages in database-level aggregations.
+//
+// The opts parameter can be used to specify options for this operation (see the options.AggregateOptions documentation).
+//
+// For more information about the command, see https://www.mongodb.com/docs/manual/reference/command/aggregate/.
+func (db *Database) Aggregate(
+ ctx context.Context,
+ pipeline any,
+ opts ...options.Lister[options.AggregateOptions],
+) (*Cursor, error) {
+ a := aggregateParams{
+ ctx: ctx,
+ pipeline: pipeline,
+ client: db.client,
+ registry: db.registry,
+ readConcern: db.readConcern,
+ writeConcern: db.writeConcern,
+ db: db.name,
+ readSelector: db.readSelector,
+ writeSelector: db.writeSelector,
+ readPreference: db.readPreference,
+ }
+
+ return aggregate(a, opts...)
+}
+
+func (db *Database) processRunCommand(
+ ctx context.Context,
+ cmd any,
+ cursorCommand bool,
+ opts ...options.Lister[options.RunCmdOptions],
+) (*operation.Command, *session.Client, error) {
+ args, err := mongoutil.NewOptions[options.RunCmdOptions](append(defaultRunCmdOpts, opts...)...)
+ if err != nil {
+ return nil, nil, fmt.Errorf("failed to construct options from builder: %w", err)
+ }
+
+ sess := sessionFromContext(ctx)
+ if sess == nil && db.client.sessionPool != nil {
+ sess = session.NewImplicitClientSession(db.client.sessionPool, db.client.id)
+ }
+
+ if err := db.client.validSession(sess); err != nil {
+ return nil, sess, err
+ }
+
+ if sess != nil && sess.TransactionRunning() && args.ReadPreference != nil && args.ReadPreference.Mode() != readpref.PrimaryMode {
+ return nil, sess, errors.New("read preference in a transaction must be primary")
+ }
+
+ if isUnorderedMap(cmd) {
+ return nil, sess, ErrMapForOrderedArgument{"cmd"}
+ }
+
+ runCmdDoc, err := marshal(cmd, db.bsonOpts, db.registry)
+ if err != nil {
+ return nil, sess, err
+ }
+
+ var readSelect description.ServerSelector
+
+ readSelect = &serverselector.Composite{
+ Selectors: []description.ServerSelector{
+ &serverselector.ReadPref{ReadPref: args.ReadPreference},
+ &serverselector.Latency{Latency: db.client.localThreshold},
+ },
+ }
+
+ if sess != nil && sess.PinnedServerAddr != nil {
+ readSelect = makePinnedSelector(sess, readSelect)
+ }
+
+ var op *operation.Command
+ switch retryOverload := db.client.retryReads && db.client.retryWrites; cursorCommand {
+ case true:
+ cursorOpts := db.client.createBaseCursorOptions(retryOverload)
+
+ cursorOpts.MarshalValueEncoderFn = newEncoderFn(db.bsonOpts, db.registry)
+
+ op = operation.NewCursorCommand(runCmdDoc, cursorOpts)
+ default:
+ op = operation.NewCommand(runCmdDoc)
+ maxAdaptiveRetries := db.client.effectiveAdaptiveRetries(retryOverload)
+ op = op.MaxAdaptiveRetries(maxAdaptiveRetries).
+ EnableOverloadRetargeting(db.client.enableOverloadRetargeting)
+ }
+
+ return op.Session(sess).CommandMonitor(db.client.monitor).
+ ServerSelector(readSelect).ClusterClock(db.client.clock).
+ Database(db.name).Deployment(db.client.deployment).
+ Crypt(db.client.cryptFLE).ReadPreference(args.ReadPreference).ServerAPI(db.client.serverAPI).
+ Timeout(db.client.timeout).Logger(db.client.logger).Authenticator(db.client.authenticator), sess, nil
+}
+
+// RunCommand executes the given command against the database.
+//
+// This function does not obey the Database's readPreference. To specify a read
+// preference, the RunCmdOptions.ReadPreference option must be used.
+//
+// This function does not obey the Database's readConcern or writeConcern. A
+// user must supply these values manually in the user-provided runCommand
+// parameter.
+//
+// The runCommand parameter must be a document for the command to be executed. It cannot be nil.
+// This must be an order-preserving type such as bson.D. Map types such as bson.M are not valid.
+//
+// The opts parameter can be used to specify options for this operation (see the options.RunCmdOptions documentation).
+//
+// The behavior of RunCommand is undefined if the command document contains any of the following:
+// - A session ID or any transaction-specific fields
+// - API versioning options when an API version is already declared on the Client
+// - maxTimeMS when Timeout is set on the Client
+func (db *Database) RunCommand(
+ ctx context.Context,
+ runCommand any,
+ opts ...options.Lister[options.RunCmdOptions],
+) *SingleResult {
+ if ctx == nil {
+ ctx = context.Background()
+ }
+
+ op, sess, err := db.processRunCommand(ctx, runCommand, false, opts...)
+ defer closeImplicitSession(sess)
+ if err != nil {
+ return &SingleResult{err: err}
+ }
+
+ err = op.Execute(ctx)
+ // RunCommand can be used to run a write, thus execute may return a write error
+ rr, convErr := processWriteError(err)
+ return &SingleResult{
+ ctx: ctx,
+ err: convErr,
+ rdr: bson.Raw(op.Result()),
+ bsonOpts: db.bsonOpts,
+ reg: db.registry,
+ Acknowledged: rr.isAcknowledged(),
+ }
+}
+
+// RunCommandCursor executes the given command against the database and parses the response as a cursor. If the command
+// being executed does not return a cursor (e.g. insert), the command will be executed on the server and an error will
+// be returned because the server response cannot be parsed as a cursor. This function does not obey the Database's read
+// preference. To specify a read preference, the RunCmdOptions.ReadPreference option must be used.
+//
+// The runCommand parameter must be a document for the command to be executed. It cannot be nil.
+// This must be an order-preserving type such as bson.D. Map types such as bson.M are not valid.
+//
+// The opts parameter can be used to specify options for this operation (see the options.RunCmdOptions documentation).
+//
+// The behavior of RunCommandCursor is undefined if the command document contains any of the following:
+// - A session ID or any transaction-specific fields
+// - API versioning options when an API version is already declared on the Client
+// - maxTimeMS when Timeout is set on the Client
+func (db *Database) RunCommandCursor(
+ ctx context.Context,
+ runCommand any,
+ opts ...options.Lister[options.RunCmdOptions],
+) (*Cursor, error) {
+ if ctx == nil {
+ ctx = context.Background()
+ }
+
+ op, sess, err := db.processRunCommand(ctx, runCommand, true, opts...)
+ if err != nil {
+ closeImplicitSession(sess)
+ return nil, wrapErrors(err)
+ }
+
+ if err = op.Execute(ctx); err != nil {
+ closeImplicitSession(sess)
+ if errors.Is(err, driver.ErrNoCursor) {
+ return nil, errors.New(
+ "database response does not contain a cursor; try using RunCommand instead")
+ }
+ return nil, wrapErrors(err)
+ }
+
+ bc, err := op.ResultCursor()
+ if err != nil {
+ closeImplicitSession(sess)
+ return nil, wrapErrors(err)
+ }
+ cursor, err := newCursorWithSession(bc, db.bsonOpts, db.registry, sess,
+ withCursorOptionClientTimeout(db.client.timeout))
+ return cursor, wrapErrors(err)
+}
+
+// Drop drops the database on the server. This method ignores "namespace not found" errors so it is safe to drop
+// a database that does not exist on the server.
+func (db *Database) Drop(ctx context.Context) error {
+ if ctx == nil {
+ ctx = context.Background()
+ }
+
+ sess := sessionFromContext(ctx)
+ if sess == nil && db.client.sessionPool != nil {
+ sess = session.NewImplicitClientSession(db.client.sessionPool, db.client.id)
+ defer sess.EndSession()
+ }
+
+ err := db.client.validSession(sess)
+ if err != nil {
+ return err
+ }
+
+ wc := db.writeConcern
+ if sess.TransactionRunning() {
+ wc = nil
+ }
+ if !wc.Acknowledged() {
+ sess = nil
+ }
+
+ selector := makePinnedSelector(sess, db.writeSelector)
+
+ op := operation.NewDropDatabase().
+ Session(sess).WriteConcern(wc).CommandMonitor(db.client.monitor).
+ ServerSelector(selector).ClusterClock(db.client.clock).
+ Database(db.name).Deployment(db.client.deployment).Crypt(db.client.cryptFLE).
+ ServerAPI(db.client.serverAPI).Authenticator(db.client.authenticator)
+
+ err = op.Execute(ctx)
+
+ var driverErr driver.Error
+ if err != nil && (!errors.As(err, &driverErr) || !driverErr.NamespaceNotFound()) {
+ return wrapErrors(err)
+ }
+ return nil
+}
+
+// ListCollectionSpecifications executes a listCollections command and returns a slice of CollectionSpecification
+// instances representing the collections in the database.
+//
+// The filter parameter must be a document containing query operators and can be used to select which collections
+// are included in the result. It cannot be nil. An empty document (e.g. bson.D{}) should be used to include all
+// collections.
+//
+// The opts parameter can be used to specify options for the operation (see the options.ListCollectionsOptions
+// documentation).
+//
+// For more information about the command, see https://www.mongodb.com/docs/manual/reference/command/listCollections/.
+func (db *Database) ListCollectionSpecifications(
+ ctx context.Context,
+ filter any,
+ opts ...options.Lister[options.ListCollectionsOptions],
+) ([]CollectionSpecification, error) {
+ cursor, err := db.ListCollections(ctx, filter, opts...)
+ if err != nil {
+ return nil, err
+ }
+
+ var resp []struct {
+ Name string `bson:"name"`
+ Type string `bson:"type"`
+ Info *struct {
+ ReadOnly bool `bson:"readOnly"`
+ UUID *bson.Binary `bson:"uuid"`
+ } `bson:"info"`
+ Options bson.Raw `bson:"options"`
+ IDIndex indexListSpecificationResponse `bson:"idIndex"`
+ }
+
+ err = cursor.All(ctx, &resp)
+ if err != nil {
+ return nil, err
+ }
+
+ specs := make([]CollectionSpecification, len(resp))
+ for idx, spec := range resp {
+ specs[idx] = CollectionSpecification{
+ Name: spec.Name,
+ Type: spec.Type,
+ Options: spec.Options,
+ IDIndex: IndexSpecification(spec.IDIndex),
+ }
+
+ if spec.Info != nil {
+ specs[idx].ReadOnly = spec.Info.ReadOnly
+ specs[idx].UUID = spec.Info.UUID
+ }
+
+ // Pre-4.4 servers report a namespace in their responses, so we only set Namespace manually if it was not in
+ // the response.
+ if specs[idx].IDIndex.Namespace == "" {
+ specs[idx].IDIndex.Namespace = db.name + "." + specs[idx].Name
+ }
+ }
+
+ return specs, nil
+}
+
+// ListCollections executes a listCollections command and returns a cursor over the collections in the database.
+//
+// The filter parameter must be a document containing query operators and can be used to select which collections
+// are included in the result. It cannot be nil. An empty document (e.g. bson.D{}) should be used to include all
+// collections.
+//
+// The opts parameter can be used to specify options for the operation (see the options.ListCollectionsOptions
+// documentation).
+//
+// For more information about the command, see https://www.mongodb.com/docs/manual/reference/command/listCollections/.
+//
+// BUG(benjirewis): ListCollections prevents listing more than 100 collections per database when running against
+// MongoDB version 2.6.
+func (db *Database) ListCollections(
+ ctx context.Context,
+ filter any,
+ opts ...options.Lister[options.ListCollectionsOptions],
+) (*Cursor, error) {
+ if ctx == nil {
+ ctx = context.Background()
+ }
+
+ args, err := mongoutil.NewOptions[options.ListCollectionsOptions](opts...)
+ if err != nil {
+ return nil, fmt.Errorf("failed to construct options from builder: %w", err)
+ }
+
+ filterDoc, err := marshal(filter, db.bsonOpts, db.registry)
+ if err != nil {
+ return nil, err
+ }
+
+ sess := sessionFromContext(ctx)
+ if sess == nil && db.client.sessionPool != nil {
+ sess = session.NewImplicitClientSession(db.client.sessionPool, db.client.id)
+ }
+
+ err = db.client.validSession(sess)
+ if err != nil {
+ closeImplicitSession(sess)
+ return nil, err
+ }
+
+ retry := driver.RetryNone
+ if db.client.retryReads {
+ retry = driver.RetryOncePerCommand
+ }
+
+ cursorOpts := db.client.createBaseCursorOptions(db.client.retryReads)
+
+ var selector description.ServerSelector
+
+ selector = &serverselector.Composite{
+ Selectors: []description.ServerSelector{
+ &serverselector.ReadPref{ReadPref: readpref.Primary()},
+ &serverselector.Latency{Latency: db.client.localThreshold},
+ },
+ }
+
+ selector = makeReadPrefSelector(sess, selector, db.client.localThreshold)
+
+ op := operation.NewListCollections(filterDoc).
+ Session(sess).ReadPreference(db.readPreference).CommandMonitor(db.client.monitor).
+ Retry(retry).MaxAdaptiveRetries(cursorOpts.MaxAdaptiveRetries).
+ EnableOverloadRetargeting(cursorOpts.EnableOverloadRetargeting).
+ ServerSelector(selector).ClusterClock(db.client.clock).
+ Database(db.name).Deployment(db.client.deployment).Crypt(db.client.cryptFLE).
+ ServerAPI(db.client.serverAPI).Timeout(db.client.timeout).Authenticator(db.client.authenticator)
+
+ cursorOpts.MarshalValueEncoderFn = newEncoderFn(db.bsonOpts, db.registry)
+
+ if args.NameOnly != nil {
+ op = op.NameOnly(*args.NameOnly)
+ }
+ if args.BatchSize != nil {
+ cursorOpts.BatchSize = *args.BatchSize
+ op = op.BatchSize(*args.BatchSize)
+ }
+ if args.AuthorizedCollections != nil {
+ op = op.AuthorizedCollections(*args.AuthorizedCollections)
+ }
+ if rawData, ok := optionsutil.Value(args.Internal, "rawData").(bool); ok {
+ op = op.RawData(rawData)
+ }
+
+ err = op.Execute(ctx)
+ if err != nil {
+ closeImplicitSession(sess)
+ return nil, wrapErrors(err)
+ }
+
+ bc, err := op.Result(cursorOpts)
+ if err != nil {
+ closeImplicitSession(sess)
+ return nil, wrapErrors(err)
+ }
+ cursor, err := newCursorWithSession(bc, db.bsonOpts, db.registry, sess,
+ withCursorOptionClientTimeout(db.client.timeout))
+ return cursor, wrapErrors(err)
+}
+
+// ListCollectionNames executes a listCollections command and returns a slice containing the names of the collections
+// in the database. This method requires driver version >= 1.1.0.
+//
+// The filter parameter must be a document containing query operators and can be used to select which collections
+// are included in the result. It cannot be nil. An empty document (e.g. bson.D{}) should be used to include all
+// collections.
+//
+// The opts parameter can be used to specify options for the operation (see the options.ListCollectionsOptions
+// documentation).
+//
+// For more information about the command, see https://www.mongodb.com/docs/manual/reference/command/listCollections/.
+//
+// BUG(benjirewis): ListCollectionNames prevents listing more than 100 collections per database when running against
+// MongoDB version 2.6.
+func (db *Database) ListCollectionNames(
+ ctx context.Context,
+ filter any,
+ opts ...options.Lister[options.ListCollectionsOptions],
+) ([]string, error) {
+ opts = append(opts, options.ListCollections().SetNameOnly(true))
+
+ res, err := db.ListCollections(ctx, filter, opts...)
+ if err != nil {
+ return nil, err
+ }
+
+ defer res.Close(ctx)
+
+ names := make([]string, 0)
+ for res.Next(ctx) {
+ elem, err := res.Current.LookupErr("name")
+ if err != nil {
+ return nil, err
+ }
+
+ if elem.Type != bson.TypeString {
+ return nil, fmt.Errorf("incorrect type for 'name'. got %v. want %v", elem.Type, bson.TypeString)
+ }
+
+ elemName := elem.StringValue()
+ names = append(names, elemName)
+ }
+
+ res.Close(ctx)
+ return names, nil
+}
+
+// Watch returns a change stream for all changes to the corresponding database. See
+// https://www.mongodb.com/docs/manual/changeStreams/ for more information about change streams.
+//
+// The Database must be configured with read concern majority or no read concern for a change stream to be created
+// successfully.
+//
+// The pipeline parameter must be a slice of documents, each representing a pipeline stage. The pipeline cannot be
+// nil but can be empty. The stage documents must all be non-nil. See https://www.mongodb.com/docs/manual/changeStreams/ for
+// a list of pipeline stages that can be used with change streams. For a pipeline of bson.D documents, the
+// mongo.Pipeline{} type can be used.
+//
+// The opts parameter can be used to specify options for change stream creation (see the options.ChangeStreamOptions
+// documentation).
+func (db *Database) Watch(ctx context.Context, pipeline any,
+ opts ...options.Lister[options.ChangeStreamOptions],
+) (*ChangeStream, error) {
+ csConfig := changeStreamConfig{
+ readConcern: db.readConcern,
+ readPreference: db.readPreference,
+ client: db.client,
+ bsonOpts: db.bsonOpts,
+ registry: db.registry,
+ streamType: DatabaseStream,
+ databaseName: db.Name(),
+ crypt: db.client.cryptFLE,
+ }
+ return newChangeStream(ctx, csConfig, pipeline, opts...)
+}
+
+// CreateCollection creates a new collection on the server with the specified
+// name and options.
+//
+// MongoDB versions < 7.0 will return an error if the collection already exists.
+// MongoDB versions >= 7.0 will not return an error if an existing collection
+// created with the same name and options already exists.
+//
+// For more information about the command, see
+// https://www.mongodb.com/docs/manual/reference/command/create/.
+func (db *Database) CreateCollection(ctx context.Context, name string, opts ...options.Lister[options.CreateCollectionOptions]) error {
+ args, err := mongoutil.NewOptions(opts...)
+ if err != nil {
+ return fmt.Errorf("failed to construct options from builder: %w", err)
+ }
+
+ // Follow In-Use Encryption specification to check for encryptedFields.
+ // Check for encryptedFields from create options.
+ ef := args.EncryptedFields
+ // Check for encryptedFields from the client EncryptedFieldsMap.
+ if ef == nil {
+ ef = db.getEncryptedFieldsFromMap(name)
+ }
+ if ef != nil {
+ return db.createCollectionWithEncryptedFields(ctx, name, ef, opts...)
+ }
+
+ return db.createCollection(ctx, name, opts...)
+}
+
+// getEncryptedFieldsFromServer tries to get an "encryptedFields" document associated with collectionName by running the "listCollections" command.
+// Returns nil and no error if the listCollections command succeeds, but "encryptedFields" is not present.
+func (db *Database) getEncryptedFieldsFromServer(ctx context.Context, collectionName string) (any, error) {
+ // Check if collection has an EncryptedFields configured server-side.
+ collSpecs, err := db.ListCollectionSpecifications(ctx, bson.D{{"name", collectionName}})
+ if err != nil {
+ return nil, err
+ }
+ if len(collSpecs) == 0 {
+ return nil, nil
+ }
+ if len(collSpecs) > 1 {
+ return nil, fmt.Errorf("expected 1 or 0 results from listCollections, got %v", len(collSpecs))
+ }
+ collSpec := collSpecs[0]
+ rawValue, err := collSpec.Options.LookupErr("encryptedFields")
+ if errors.Is(err, bsoncore.ErrElementNotFound) {
+ return nil, nil
+ } else if err != nil {
+ return nil, err
+ }
+
+ encryptedFields, ok := rawValue.DocumentOK()
+ if !ok {
+ return nil, fmt.Errorf("expected encryptedFields of %v to be document, got %v", collectionName, rawValue.Type)
+ }
+
+ return encryptedFields, nil
+}
+
+// getEncryptedFieldsFromMap tries to get an "encryptedFields" document associated with collectionName by checking the client EncryptedFieldsMap.
+// Returns nil and no error if an EncryptedFieldsMap is not configured, or does not contain an entry for collectionName.
+func (db *Database) getEncryptedFieldsFromMap(collectionName string) any {
+ // Check the EncryptedFieldsMap
+ efMap := db.client.encryptedFieldsMap
+ if efMap == nil {
+ return nil
+ }
+
+ namespace := db.name + "." + collectionName
+
+ ef, ok := efMap[namespace]
+ if ok {
+ return ef
+ }
+ return nil
+}
+
+// createCollectionWithEncryptedFields creates a collection with an EncryptedFields.
+func (db *Database) createCollectionWithEncryptedFields(
+ ctx context.Context,
+ name string,
+ ef any,
+ opts ...options.Lister[options.CreateCollectionOptions],
+) error {
+ efBSON, err := marshal(ef, db.bsonOpts, db.registry)
+ if err != nil {
+ return fmt.Errorf("error transforming document: %w", err)
+ }
+
+ // Check the wire version to ensure server is 7.0.0 or newer.
+ // After the wire version check, and before creating the collections, it is possible the server state changes.
+ // That is OK. This wire version check is a best effort to inform users earlier if using a QEv2 driver with a QEv1 server.
+ {
+ const QEv2WireVersion = 21
+ ctx, cancel := csot.WithServerSelectionTimeout(ctx, db.client.deployment.GetServerSelectionTimeout())
+ defer cancel()
+
+ server, err := db.client.deployment.SelectServer(ctx, &serverselector.Write{})
+ if err != nil {
+ return fmt.Errorf("error selecting server to check maxWireVersion: %w", err)
+ }
+
+ conn, err := server.Connection(ctx)
+ if err != nil {
+ return fmt.Errorf("error getting connection to check maxWireVersion: %w", err)
+ }
+ defer conn.Close()
+ wireVersionRange := conn.Description().WireVersion
+ if wireVersionRange.Max < QEv2WireVersion {
+ return fmt.Errorf("driver support of Queryable Encryption is incompatible with server. Upgrade server to use Queryable Encryption. Got maxWireVersion %v but need maxWireVersion >= %v", wireVersionRange.Max, QEv2WireVersion)
+ }
+ }
+
+ // Create the two encryption-related, associated collections: `escCollection` and `ecocCollection`.
+
+ stateCollectionOpts := options.CreateCollection().
+ SetClusteredIndex(bson.D{{"key", bson.D{{"_id", 1}}}, {"unique", true}})
+ // Create ESCCollection.
+ escCollection, err := csfle.GetEncryptedStateCollectionName(efBSON, name, csfle.EncryptedStateCollection)
+ if err != nil {
+ return err
+ }
+
+ if err := db.createCollection(ctx, escCollection, stateCollectionOpts); err != nil {
+ return err
+ }
+
+ // Create ECOCCollection.
+ ecocCollection, err := csfle.GetEncryptedStateCollectionName(efBSON, name, csfle.EncryptedCompactionCollection)
+ if err != nil {
+ return err
+ }
+
+ if err := db.createCollection(ctx, ecocCollection, stateCollectionOpts); err != nil {
+ return err
+ }
+
+ // Create a data collection with the 'encryptedFields' option.
+ op, err := db.createCollectionOperation(name, opts...)
+ if err != nil {
+ return err
+ }
+
+ op.EncryptedFields(efBSON)
+ if err := db.executeCreateOperation(ctx, op); err != nil {
+ return err
+ }
+
+ // Create an index on the __safeContent__ field in the collection @collectionName.
+ if _, err := db.Collection(name).Indexes().CreateOne(ctx, IndexModel{Keys: bson.D{{"__safeContent__", 1}}}); err != nil {
+ return fmt.Errorf("error creating safeContent index: %w", err)
+ }
+
+ return nil
+}
+
+// createCollection creates a collection without EncryptedFields.
+func (db *Database) createCollection(
+ ctx context.Context,
+ name string,
+ opts ...options.Lister[options.CreateCollectionOptions],
+) error {
+ op, err := db.createCollectionOperation(name, opts...)
+ if err != nil {
+ return err
+ }
+ return db.executeCreateOperation(ctx, op)
+}
+
+func (db *Database) createCollectionOperation(
+ name string,
+ opts ...options.Lister[options.CreateCollectionOptions],
+) (*operation.Create, error) {
+ args, err := mongoutil.NewOptions[options.CreateCollectionOptions](opts...)
+ if err != nil {
+ return nil, fmt.Errorf("failed to construct options from builder: %w", err)
+ }
+
+ op := operation.NewCreate(name).ServerAPI(db.client.serverAPI).Authenticator(db.client.authenticator)
+
+ if args.Capped != nil {
+ op.Capped(*args.Capped)
+ }
+ if args.Collation != nil {
+ op.Collation(bsoncore.Document(toDocument(args.Collation)))
+ }
+ if args.ChangeStreamPreAndPostImages != nil {
+ csppi, err := marshal(args.ChangeStreamPreAndPostImages, db.bsonOpts, db.registry)
+ if err != nil {
+ return nil, err
+ }
+ op.ChangeStreamPreAndPostImages(csppi)
+ }
+ if args.DefaultIndexOptions != nil {
+ defaultIndexArgs, err := mongoutil.NewOptions[options.DefaultIndexOptions](args.DefaultIndexOptions)
+ if err != nil {
+ return nil, fmt.Errorf("failed to construct DefaultIndexArgs from options: %w", err)
+ }
+
+ idx, doc := bsoncore.AppendDocumentStart(nil)
+ if defaultIndexArgs.StorageEngine != nil {
+ storageEngine, err := marshal(defaultIndexArgs.StorageEngine, db.bsonOpts, db.registry)
+ if err != nil {
+ return nil, err
+ }
+
+ doc = bsoncore.AppendDocumentElement(doc, "storageEngine", storageEngine)
+ }
+
+ doc, err = bsoncore.AppendDocumentEnd(doc, idx)
+ if err != nil {
+ return nil, err
+ }
+
+ op.IndexOptionDefaults(doc)
+ }
+ if args.MaxDocuments != nil {
+ op.Max(*args.MaxDocuments)
+ }
+ if args.SizeInBytes != nil {
+ op.Size(*args.SizeInBytes)
+ }
+ if args.StorageEngine != nil {
+ storageEngine, err := marshal(args.StorageEngine, db.bsonOpts, db.registry)
+ if err != nil {
+ return nil, err
+ }
+ op.StorageEngine(storageEngine)
+ }
+ if args.ValidationAction != nil {
+ op.ValidationAction(*args.ValidationAction)
+ }
+ if args.ValidationLevel != nil {
+ op.ValidationLevel(*args.ValidationLevel)
+ }
+ if args.Validator != nil {
+ validator, err := marshal(args.Validator, db.bsonOpts, db.registry)
+ if err != nil {
+ return nil, err
+ }
+ op.Validator(validator)
+ }
+ if args.ExpireAfterSeconds != nil {
+ op.ExpireAfterSeconds(*args.ExpireAfterSeconds)
+ }
+ if args.TimeSeriesOptions != nil {
+ timeSeriesArgs, err := mongoutil.NewOptions[options.TimeSeriesOptions](args.TimeSeriesOptions)
+ if err != nil {
+ return nil, fmt.Errorf("failed to construct DefaultIndexArgs from options: %w", err)
+ }
+
+ idx, doc := bsoncore.AppendDocumentStart(nil)
+ doc = bsoncore.AppendStringElement(doc, "timeField", timeSeriesArgs.TimeField)
+
+ if timeSeriesArgs.MetaField != nil {
+ doc = bsoncore.AppendStringElement(doc, "metaField", *timeSeriesArgs.MetaField)
+ }
+ if timeSeriesArgs.Granularity != nil {
+ doc = bsoncore.AppendStringElement(doc, "granularity", *timeSeriesArgs.Granularity)
+ }
+
+ if timeSeriesArgs.BucketMaxSpan != nil {
+ bmss := int64(*timeSeriesArgs.BucketMaxSpan / time.Second)
+
+ doc = bsoncore.AppendInt64Element(doc, "bucketMaxSpanSeconds", bmss)
+ }
+
+ if timeSeriesArgs.BucketRounding != nil {
+ brs := int64(*timeSeriesArgs.BucketRounding / time.Second)
+
+ doc = bsoncore.AppendInt64Element(doc, "bucketRoundingSeconds", brs)
+ }
+
+ doc, err = bsoncore.AppendDocumentEnd(doc, idx)
+ if err != nil {
+ return nil, err
+ }
+
+ op.TimeSeries(doc)
+ }
+ if args.ClusteredIndex != nil {
+ clusteredIndex, err := marshal(args.ClusteredIndex, db.bsonOpts, db.registry)
+ if err != nil {
+ return nil, err
+ }
+ op.ClusteredIndex(clusteredIndex)
+ }
+
+ return op, nil
+}
+
+// CreateView creates a view on the server.
+//
+// The viewName parameter specifies the name of the view to create. The viewOn
+// parameter specifies the name of the collection or view on which this view
+// will be created. The pipeline parameter specifies an aggregation pipeline
+// that will be exececuted against the source collection or view to create this
+// view.
+//
+// MongoDB versions < 7.0 will return an error if the view already exists.
+// MongoDB versions >= 7.0 will not return an error if an existing view created
+// with the same name and options already exists.
+//
+// See https://www.mongodb.com/docs/manual/core/views/ for more information
+// about views.
+func (db *Database) CreateView(ctx context.Context, viewName, viewOn string, pipeline any,
+ opts ...options.Lister[options.CreateViewOptions],
+) error {
+ pipelineArray, _, err := marshalAggregatePipeline(pipeline, db.bsonOpts, db.registry)
+ if err != nil {
+ return err
+ }
+
+ op := operation.NewCreate(viewName).
+ ViewOn(viewOn).
+ Pipeline(pipelineArray).
+ ServerAPI(db.client.serverAPI).Authenticator(db.client.authenticator)
+ args, err := mongoutil.NewOptions(opts...)
+ if err != nil {
+ return fmt.Errorf("failed to construct options from builder: %w", err)
+ }
+
+ if args.Collation != nil {
+ op.Collation(bsoncore.Document(toDocument(args.Collation)))
+ }
+
+ return db.executeCreateOperation(ctx, op)
+}
+
+func (db *Database) executeCreateOperation(ctx context.Context, op *operation.Create) error {
+ sess := sessionFromContext(ctx)
+ if sess == nil && db.client.sessionPool != nil {
+ sess = session.NewImplicitClientSession(db.client.sessionPool, db.client.id)
+ defer sess.EndSession()
+ }
+
+ err := db.client.validSession(sess)
+ if err != nil {
+ return err
+ }
+
+ wc := db.writeConcern
+ if sess.TransactionRunning() {
+ wc = nil
+ }
+ if !wc.Acknowledged() {
+ sess = nil
+ }
+
+ selector := makePinnedSelector(sess, db.writeSelector)
+ op = op.Session(sess).
+ WriteConcern(wc).
+ CommandMonitor(db.client.monitor).
+ ServerSelector(selector).
+ ClusterClock(db.client.clock).
+ Database(db.name).
+ Deployment(db.client.deployment).
+ Crypt(db.client.cryptFLE)
+
+ return wrapErrors(op.Execute(ctx))
+}
+
+// GridFSBucket is used to construct a GridFS bucket which can be used as a
+// container for files.
+func (db *Database) GridFSBucket(opts ...options.Lister[options.BucketOptions]) *GridFSBucket {
+ b := &GridFSBucket{
+ name: "fs",
+ chunkSize: DefaultGridFSChunkSize,
+ db: db,
+ }
+
+ bo, _ := mongoutil.NewOptions[options.BucketOptions](opts...)
+ if bo.Name != nil {
+ b.name = *bo.Name
+ }
+ if bo.ChunkSizeBytes != nil {
+ b.chunkSize = *bo.ChunkSizeBytes
+ }
+ if bo.WriteConcern != nil {
+ b.wc = bo.WriteConcern
+ }
+ if bo.ReadConcern != nil {
+ b.rc = bo.ReadConcern
+ }
+ if bo.ReadPreference != nil {
+ b.rp = bo.ReadPreference
+ }
+
+ collOpts := options.Collection().SetWriteConcern(b.wc).SetReadConcern(b.rc).SetReadPreference(b.rp)
+
+ b.chunksColl = db.Collection(b.name+".chunks", collOpts)
+ b.filesColl = db.Collection(b.name+".files", collOpts)
+ b.readBuf = make([]byte, b.chunkSize)
+ b.writeBuf = make([]byte, b.chunkSize)
+
+ return b
+}
diff --git a/vendor/go.mongodb.org/mongo-driver/v2/mongo/doc.go b/vendor/go.mongodb.org/mongo-driver/v2/mongo/doc.go
new file mode 100644
index 0000000..3756d58
--- /dev/null
+++ b/vendor/go.mongodb.org/mongo-driver/v2/mongo/doc.go
@@ -0,0 +1,164 @@
+// Copyright (C) MongoDB, Inc. 2017-present.
+//
+// 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
+
+// NOTE: This documentation should be kept in line with the Example* test functions.
+
+// Package mongo provides a MongoDB Driver API for Go.
+//
+// Basic usage of the driver starts with creating a Client from a connection
+// string. To do so, call Connect:
+//
+// ctx, cancel := context.WithTimeout(context.Background(), 20*time.Second)
+// defer cancel()
+// client, err := mongo.Connect( options.Client().ApplyURI("mongodb://foo:bar@localhost:27017"))
+// if err != nil { return err }
+//
+// This will create a new client and start monitoring the MongoDB server on localhost.
+// The Database and Collection types can be used to access the database:
+//
+// collection := client.Database("baz").Collection("qux")
+//
+// A Collection can be used to query the database or insert documents:
+//
+// res, err := collection.InsertOne(context.Background(), bson.M{"hello": "world"})
+// if err != nil { return err }
+// id := res.InsertedID
+//
+// Several methods return a cursor, which can be used like this:
+//
+// cur, err := collection.Find(context.Background(), bson.D{})
+// if err != nil { log.Fatal(err) }
+// defer cur.Close(context.Background())
+// for cur.Next(context.Background()) {
+// // To decode into a struct, use cursor.Decode()
+// result := struct{
+// Foo string
+// Bar int32
+// }{}
+// err := cur.Decode(&result)
+// if err != nil { log.Fatal(err) }
+// // do something with result...
+//
+// // To get the raw bson bytes use cursor.Current
+// raw := cur.Current
+// // do something with raw...
+// }
+// if err := cur.Err(); err != nil {
+// return err
+// }
+//
+// Cursor.All will decode all of the returned elements at once:
+//
+// var results []struct{
+// Foo string
+// Bar int32
+// }
+// if err = cur.All(context.Background(), &results); err != nil {
+// log.Fatal(err)
+// }
+// // do something with results...
+//
+// Methods that only return a single document will return a *SingleResult, which works
+// like a *sql.Row:
+//
+// result := struct{
+// Foo string
+// Bar int32
+// }{}
+// filter := bson.D{{"hello", "world"}}
+// err := collection.FindOne(context.Background(), filter).Decode(&result)
+// if err != nil { return err }
+// // do something with result...
+//
+// All Client, Collection, and Database methods that take parameters of type any
+// will return ErrNilDocument if nil is passed in for an any.
+//
+// Additional examples can be found under the examples directory in the driver's repository and
+// on the MongoDB website.
+//
+// # Error Handling
+//
+// Errors from the MongoDB server will implement the ServerError interface, which has functions to check for specific
+// error codes, labels, and message substrings. These can be used to check for and handle specific errors. Some methods,
+// like InsertMany and BulkWrite, can return an error representing multiple errors, and in those cases the ServerError
+// functions will return true if any of the contained errors satisfy the check.
+//
+// There are also helper functions to check for certain specific types of errors:
+//
+// IsDuplicateKeyError(error)
+// IsNetworkError(error)
+// IsTimeout(error)
+//
+// # Potential DNS Issues
+//
+// Building with Go 1.11+ and using connection strings with the "mongodb+srv"[1] scheme is unfortunately
+// incompatible with some DNS servers in the wild due to the change introduced in
+// https://github.com/golang/go/issues/10622. You may receive an error with the message "cannot unmarshal DNS message"
+// while running an operation when using DNS servers that non-compliantly compress SRV records. Old versions of kube-dns
+// and the native DNS resolver (systemd-resolver) on Ubuntu 18.04 are known to be non-compliant in this manner. We suggest
+// using a different DNS server (8.8.8.8 is the common default), and, if that's not possible, avoiding the "mongodb+srv"
+// scheme.
+//
+// # In-Use Encryption
+//
+// MongoDB provides two approaches to In-Use Encryption: Queryable Encryption (QE) and Client-Side Field Level Encryption (CSFLE).
+//
+// The Queryable Encryption and CSFLE features share much of the same API with some exceptions.
+//
+// - AutoEncryptionOptions.SetEncryptedFieldsMap only applies to Queryable Encryption.
+// - AutoEncryptionOptions.SetSchemaMap only applies to CSFLE.
+//
+// In-use encryption is a new feature in MongoDB 4.2 that allows specific data fields to be encrypted. Using this
+// feature requires specifying the "cse" build tag during compilation:
+//
+// go build -tags cse
+//
+// Note: Auto encryption is an enterprise- and Atlas-only feature.
+//
+// The libmongocrypt C library is required when using in-use encryption. Specific versions of libmongocrypt
+// are required for different versions of the Go Driver:
+//
+// - Go Driver v1.2.0 requires libmongocrypt v1.0.0 or higher
+//
+// - Go Driver v1.5.0 requires libmongocrypt v1.1.0 or higher
+//
+// - Go Driver v1.8.0 requires libmongocrypt v1.3.0 or higher
+//
+// - Go Driver v1.10.0 requires libmongocrypt v1.5.0 or higher.
+// There is a severe bug when calling RewrapManyDataKey with libmongocrypt versions less than 1.5.2.
+// This bug may result in data corruption.
+// Please use libmongocrypt 1.5.2 or higher when calling RewrapManyDataKey.
+//
+// - Go Driver v1.12.0 requires libmongocrypt v1.8.0 or higher.
+//
+// To install libmongocrypt, follow the instructions for your
+// operating system:
+//
+// 1. Linux: follow the instructions listed at
+// https://github.com/mongodb/libmongocrypt#installing-libmongocrypt-from-distribution-packages to install the correct
+// deb/rpm package.
+//
+// 2. Mac: Follow the instructions listed at https://github.com/mongodb/libmongocrypt#installing-libmongocrypt-on-macos
+// to install packages via brew and compile the libmongocrypt source code.
+//
+// 3. Windows:
+//
+// mkdir -p c:/libmongocrypt/bin
+// mkdir -p c:/libmongocrypt/include
+//
+// // Run the curl command in an empty directory as it will create new directories when unpacked.
+// curl https://s3.amazonaws.com/mciuploads/libmongocrypt/windows/latest_release/libmongocrypt.tar.gz --output libmongocrypt.tar.gz
+// tar -xvzf libmongocrypt.tar.gz
+//
+// cp ./bin/mongocrypt.dll c:/libmongocrypt/bin
+// cp ./include/mongocrypt/*.h c:/libmongocrypt/include
+// export PATH=$PATH:/cygdrive/c/libmongocrypt/bin
+//
+// libmongocrypt communicates with the mongocryptd process or mongo_crypt shared library for automatic encryption.
+// See AutoEncryptionOpts.SetExtraOptions for options to configure use of mongocryptd or mongo_crypt.
+//
+// [1] See https://www.mongodb.com/docs/manual/reference/connection-string/#dns-seedlist-connection-format
+package mongo
diff --git a/vendor/go.mongodb.org/mongo-driver/v2/mongo/errors.go b/vendor/go.mongodb.org/mongo-driver/v2/mongo/errors.go
new file mode 100644
index 0000000..b328426
--- /dev/null
+++ b/vendor/go.mongodb.org/mongo-driver/v2/mongo/errors.go
@@ -0,0 +1,983 @@
+// Copyright (C) MongoDB, Inc. 2017-present.
+//
+// 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
+
+package mongo
+
+import (
+ "bytes"
+ "context"
+ "errors"
+ "fmt"
+ "net"
+ "reflect"
+ "strings"
+
+ "go.mongodb.org/mongo-driver/v2/bson"
+ "go.mongodb.org/mongo-driver/v2/internal/codecutil"
+ "go.mongodb.org/mongo-driver/v2/x/mongo/driver"
+ "go.mongodb.org/mongo-driver/v2/x/mongo/driver/mongocrypt"
+ "go.mongodb.org/mongo-driver/v2/x/mongo/driver/topology"
+)
+
+// ErrClientDisconnected is returned when disconnected Client is used to run an operation.
+var ErrClientDisconnected = errors.New("client is disconnected")
+
+// InvalidArgumentError wraps an invalid argument error.
+type InvalidArgumentError struct {
+ wrapped error
+}
+
+// Error implements the error interface.
+func (e InvalidArgumentError) Error() string {
+ return e.wrapped.Error()
+}
+
+// Unwrap returns the underlying error.
+func (e InvalidArgumentError) Unwrap() error {
+ return e.wrapped
+}
+
+// ErrMultipleIndexDrop is returned if multiple indexes would be dropped from a call to IndexView.DropOne.
+var ErrMultipleIndexDrop error = InvalidArgumentError{errors.New("multiple indexes would be dropped")}
+
+// ErrNilDocument is returned when a nil document is passed to a CRUD method.
+var ErrNilDocument error = InvalidArgumentError{errors.New("document is nil")}
+
+// ErrNilValue is returned when a nil value is passed to a CRUD method.
+var ErrNilValue error = InvalidArgumentError{errors.New("value is nil")}
+
+// ErrEmptySlice is returned when an empty slice is passed to a CRUD method that requires a non-empty slice.
+var ErrEmptySlice error = InvalidArgumentError{errors.New("must provide at least one element in input slice")}
+
+// ErrNotSlice is returned when a type other than slice is passed to InsertMany.
+var ErrNotSlice error = InvalidArgumentError{errors.New("must provide a non-empty slice")}
+
+// ErrMapForOrderedArgument is returned when a map with multiple keys is passed to a CRUD method for an ordered parameter
+type ErrMapForOrderedArgument struct {
+ ParamName string
+}
+
+// Error implements the error interface.
+func (e ErrMapForOrderedArgument) Error() string {
+ return fmt.Sprintf("multi-key map passed in for ordered parameter %v", e.ParamName)
+}
+
+// wrapErrors wraps error types and values that are defined in "internal" and
+// "x" packages with error types and values that are defined in this package.
+// That allows users to inspect the errors using errors.Is/errors.As without
+// relying on "internal" or "x" packages.
+func wrapErrors(err error) error {
+ // Return nil when err is nil to avoid costly reflection logic below.
+ if err == nil {
+ return nil
+ }
+
+ // Do not propagate the acknowledgement sentinel error. For DDL commands,
+ // (creating indexes, dropping collections, etc) acknowledgement should be
+ // ignored. For non-DDL write commands (insert, update, etc), acknowledgement
+ // should be be propagated at the result-level: e.g.,
+ // SingleResult.Acknowledged.
+ if errors.Is(err, driver.ErrUnacknowledgedWrite) {
+ return nil
+ }
+ if errors.Is(err, topology.ErrTopologyClosed) {
+ return ErrClientDisconnected
+ }
+
+ var de driver.Error
+ if errors.As(err, &de) {
+ return CommandError{
+ Code: de.Code,
+ Message: de.Message,
+ Labels: de.Labels,
+ Name: de.Name,
+ Wrapped: err,
+ Raw: bson.Raw(de.Raw),
+
+ // Set wrappedMsgOnly=true here so that the Code and Message are not
+ // repeated multiple times in the error string. We expect that the
+ // wrapped driver.Error already contains that info in the error
+ // string.
+ wrappedMsgOnly: true,
+ }
+ }
+
+ var qe driver.QueryFailureError
+ if errors.As(err, &qe) {
+ // qe.Message is "command failure"
+ ce := CommandError{
+ Name: qe.Message,
+ Wrapped: err,
+ Raw: bson.Raw(qe.Response),
+
+ // Don't set wrappedMsgOnly=true here because the code below adds
+ // additional error context that is not provided by the
+ // driver.QueryFailureError. Additionally, driver.QueryFailureError
+ // is only returned when parsing OP_QUERY replies (OP_REPLY), so
+ // it's unlikely this block will ever be run now that MongoDB 3.6 is
+ // no longer supported.
+ }
+
+ dollarErr, err := qe.Response.LookupErr("$err")
+ if err == nil {
+ ce.Message, _ = dollarErr.StringValueOK()
+ }
+ code, err := qe.Response.LookupErr("code")
+ if err == nil {
+ ce.Code, _ = code.Int32OK()
+ }
+
+ return ce
+ }
+
+ var me mongocrypt.Error
+ if errors.As(err, &me) {
+ return MongocryptError{
+ Code: me.Code,
+ Message: me.Message,
+ wrapped: err,
+
+ // Set wrappedMsgOnly=true here so that the Code and Message are not
+ // repeated multiple times in the error string. We expect that the
+ // wrapped mongocrypt.Error already contains that info in the error
+ // string.
+ wrappedMsgOnly: true,
+ }
+ }
+
+ if errors.Is(err, codecutil.ErrNilValue) {
+ return ErrNilValue
+ }
+
+ var marshalErr codecutil.MarshalError
+ if errors.As(err, &marshalErr) {
+ return MarshalError{
+ Value: marshalErr.Value,
+ Err: err,
+
+ // Set wrappedMsgOnly=true here so that the Value is not repeated
+ // multiple times in the error string. We expect that the wrapped
+ // codecutil.MarshalError already contains that info in the error
+ // string.
+ wrappedMsgOnly: true,
+ }
+ }
+
+ return err
+}
+
+// IsDuplicateKeyError returns true if err is a duplicate key error. For BulkWriteExceptions,
+// IsDuplicateKeyError returns true if at least one of the errors is a duplicate key error.
+func IsDuplicateKeyError(err error) bool {
+ if se := ServerError(nil); errors.As(err, &se) {
+ return se.HasErrorCode(11000) || // Duplicate key error.
+ se.HasErrorCode(11001) || // Duplicate key error on update.
+ // Duplicate key error in a capped collection. See SERVER-7164.
+ se.HasErrorCode(12582) ||
+ // Mongos insert error caused by a duplicate key error. See
+ // SERVER-11493.
+ se.HasErrorCodeWithMessage(16460, " E11000 ")
+ }
+ return false
+}
+
+// timeoutErrs is a list of error values that indicate a timeout happened.
+var timeoutErrs = [...]error{
+ context.DeadlineExceeded,
+ driver.ErrDeadlineWouldBeExceeded,
+}
+
+// IsTimeout returns true if err was caused by a timeout. For error chains,
+// IsTimeout returns true if any error in the chain was caused by a timeout.
+func IsTimeout(err error) bool {
+ // Check if the error chain contains any of the timeout error values.
+ for _, target := range timeoutErrs {
+ if errors.Is(err, target) {
+ return true
+ }
+ }
+
+ // Check if the error chain contains any error types that can indicate
+ // timeout.
+ if errors.As(err, &topology.WaitQueueTimeoutError{}) {
+ return true
+ }
+ if errors.As(err, &timeoutError{}) {
+ return true
+ }
+ if ce := (CommandError{}); errors.As(err, &ce) && ce.IsMaxTimeMSExpiredError() {
+ return true
+ }
+ if we := (WriteException{}); errors.As(err, &we) && we.WriteConcernError != nil && we.WriteConcernError.IsMaxTimeMSExpiredError() {
+ return true
+ }
+ if ne := net.Error(nil); errors.As(err, &ne) {
+ return ne.Timeout()
+ }
+ // Check timeout error labels.
+ if le := LabeledError(nil); errors.As(err, &le) {
+ if le.HasErrorLabel("NetworkTimeoutError") || le.HasErrorLabel("ExceededTimeLimitError") {
+ return true
+ }
+ }
+
+ return false
+}
+
+// errorHasLabel returns true if err contains the specified label
+func errorHasLabel(err error, label string) bool {
+ var le LabeledError
+ return errors.As(err, &le) && le.HasErrorLabel(label)
+}
+
+// IsNetworkError returns true if err is a network error
+func IsNetworkError(err error) bool {
+ return errorHasLabel(err, "NetworkError")
+}
+
+// MarshalError is returned when attempting to marshal a value into a document
+// results in an error.
+type MarshalError struct {
+ Value any
+ Err error
+
+ // If wrappedMsgOnly is true, Error() only returns the error message from
+ // the "Err" error.
+ //
+ // This is typically only set by the wrapErrors function, which uses
+ // MarshalError to wrap codecutil.MarshalError, allowing users to access the
+ // "Value" from the underlying error but preventing duplication in the error
+ // string.
+ wrappedMsgOnly bool
+}
+
+// Error implements the error interface.
+func (me MarshalError) Error() string {
+ // If the MarshalError was created with wrappedMsgOnly=true, only return the
+ // error from the wrapped error. See the MarshalError.wrappedMsgOnly docs
+ // for more info.
+ if me.wrappedMsgOnly {
+ return me.Err.Error()
+ }
+
+ return fmt.Sprintf("cannot marshal type %s to a BSON Document: %v", reflect.TypeOf(me.Value), me.Err)
+}
+
+func (me MarshalError) Unwrap() error { return me.Err }
+
+// MongocryptError represents an libmongocrypt error during in-use encryption.
+type MongocryptError struct {
+ Code int32
+ Message string
+ wrapped error
+
+ // If wrappedMsgOnly is true, Error() only returns the error message from
+ // the "wrapped" error.
+ //
+ // This is typically only set by the wrapErrors function, which uses
+ // MarshalError to wrap mongocrypt.Error, allowing users to access the
+ // "Code" and "Message" from the underlying error but preventing duplication
+ // in the error string.
+ wrappedMsgOnly bool
+}
+
+// Error implements the error interface.
+func (m MongocryptError) Error() string {
+ // If the MongocryptError was created with wrappedMsgOnly=true, only return
+ // the error from the wrapped error. See the MongocryptError.wrappedMsgOnly
+ // docs for more info.
+ if m.wrappedMsgOnly {
+ return m.wrapped.Error()
+ }
+
+ return fmt.Sprintf("mongocrypt error %d: %v", m.Code, m.Message)
+}
+
+// Unwrap returns the underlying error.
+func (m MongocryptError) Unwrap() error { return m.wrapped }
+
+// EncryptionKeyVaultError represents an error while communicating with the key vault collection during in-use
+// encryption.
+type EncryptionKeyVaultError struct {
+ Wrapped error
+}
+
+// Error implements the error interface.
+func (ekve EncryptionKeyVaultError) Error() string {
+ return fmt.Sprintf("key vault communication error: %v", ekve.Wrapped)
+}
+
+// Unwrap returns the underlying error.
+func (ekve EncryptionKeyVaultError) Unwrap() error {
+ return ekve.Wrapped
+}
+
+// MongocryptdError represents an error while communicating with mongocryptd during in-use encryption.
+type MongocryptdError struct {
+ Wrapped error
+}
+
+// Error implements the error interface.
+func (e MongocryptdError) Error() string {
+ return fmt.Sprintf("mongocryptd communication error: %v", e.Wrapped)
+}
+
+// Unwrap returns the underlying error.
+func (e MongocryptdError) Unwrap() error {
+ return e.Wrapped
+}
+
+// LabeledError is an interface for errors with labels.
+type LabeledError interface {
+ error
+ // HasErrorLabel returns true if the error contains the specified label.
+ HasErrorLabel(string) bool
+}
+
+type errorCoder interface {
+ ErrorCodes() []int
+}
+
+var _ errorCoder = ServerError(nil)
+
+// ServerError is the interface implemented by errors returned from the server. Custom implementations of this
+// interface should not be used in production.
+type ServerError interface {
+ LabeledError
+ // HasErrorCode returns true if the error has the specified code.
+ HasErrorCode(int) bool
+ // HasErrorMessage returns true if the error contains the specified message.
+ HasErrorMessage(string) bool
+ // HasErrorCodeWithMessage returns true if any of the contained errors have the specified code and message.
+ HasErrorCodeWithMessage(int, string) bool
+
+ // ErrorCodes returns all error codes (unsorted) in the server’s response.
+ // This would include nested errors (e.g., write concern errors) for
+ // supporting implementations (e.g., BulkWriteException) as well as the
+ // top-level error code.
+ ErrorCodes() []int
+
+ serverError()
+}
+
+func hasErrorCode(srvErr ServerError, code int) bool {
+ for _, srvErrCode := range srvErr.ErrorCodes() {
+ if code == srvErrCode {
+ return true
+ }
+ }
+
+ return false
+}
+
+var (
+ _ ServerError = CommandError{}
+ _ ServerError = WriteError{}
+ _ ServerError = WriteException{}
+ _ ServerError = BulkWriteException{}
+)
+
+var (
+ _ error = ClientBulkWriteException{}
+ _ errorCoder = ClientBulkWriteException{}
+)
+
+// CommandError represents a server error during execution of a command. This can be returned by any operation.
+type CommandError struct {
+ Code int32
+ Message string
+ Labels []string // Categories to which the error belongs
+ Name string // A human-readable name corresponding to the error code
+ Wrapped error // The underlying error, if one exists.
+ Raw bson.Raw // The original server response containing the error.
+
+ // If wrappedMsgOnly is true, Error() only returns the error message from
+ // the "Wrapped" error.
+ //
+ // This is typically only set by the wrapErrors function, which uses
+ // CommandError to wrap driver.Error, allowing users to access the "Code",
+ // "Message", "Labels", "Name", and "Raw" from the underlying error but
+ // preventing duplication in the error string.
+ wrappedMsgOnly bool
+}
+
+// Error implements the error interface.
+func (e CommandError) Error() string {
+ // If the CommandError was created with wrappedMsgOnly=true, only return the
+ // error from the wrapped error. See the CommandError.wrappedMsgOnly docs
+ // for more info.
+ if e.wrappedMsgOnly {
+ return e.Wrapped.Error()
+ }
+
+ var msg string
+ if e.Name != "" {
+ msg += fmt.Sprintf("(%v)", e.Name)
+ }
+ if e.Message != "" {
+ msg += " " + e.Message
+ }
+ if e.Wrapped != nil {
+ msg += ": " + e.Wrapped.Error()
+ }
+
+ return msg
+}
+
+// Unwrap returns the underlying error.
+func (e CommandError) Unwrap() error {
+ return e.Wrapped
+}
+
+// HasErrorCode returns true if the error has the specified code.
+func (e CommandError) HasErrorCode(code int) bool {
+ return int(e.Code) == code
+}
+
+// ErrorCodes returns a list of error codes returned by the server.
+func (e CommandError) ErrorCodes() []int {
+ return []int{int(e.Code)}
+}
+
+// HasErrorLabel returns true if the error contains the specified label.
+func (e CommandError) HasErrorLabel(label string) bool {
+ for _, l := range e.Labels {
+ if l == label {
+ return true
+ }
+ }
+ return false
+}
+
+// HasErrorMessage returns true if the error contains the specified message.
+func (e CommandError) HasErrorMessage(message string) bool {
+ return strings.Contains(e.Message, message)
+}
+
+// HasErrorCodeWithMessage returns true if the error has the specified code and Message contains the specified message.
+func (e CommandError) HasErrorCodeWithMessage(code int, message string) bool {
+ return int(e.Code) == code && strings.Contains(e.Message, message)
+}
+
+// IsMaxTimeMSExpiredError returns true if the error is a MaxTimeMSExpired error.
+func (e CommandError) IsMaxTimeMSExpiredError() bool {
+ return e.Code == 50 || e.Name == "MaxTimeMSExpired"
+}
+
+// serverError implements the ServerError interface.
+func (e CommandError) serverError() {}
+
+// WriteError is an error that occurred during execution of a write operation. This error type is only returned as part
+// of a WriteException or BulkWriteException.
+type WriteError struct {
+ // The index of the write in the slice passed to an InsertMany or BulkWrite operation that caused this error.
+ Index int
+
+ Code int
+ Message string
+ Details bson.Raw
+
+ // The original write error from the server response.
+ Raw bson.Raw
+}
+
+func (we WriteError) Error() string {
+ msg := we.Message
+ if len(we.Details) > 0 {
+ msg = fmt.Sprintf("%s: %s", msg, we.Details.String())
+ }
+ return msg
+}
+
+// HasErrorCode returns true if the error has the specified code.
+func (we WriteError) HasErrorCode(code int) bool {
+ return we.Code == code
+}
+
+// ErrorCodes returns a list of error codes returned by the server.
+func (we WriteError) ErrorCodes() []int {
+ return []int{we.Code}
+}
+
+// HasErrorLabel returns true if the error contains the specified label. WriteErrors do not contain labels,
+// so we always return false.
+func (we WriteError) HasErrorLabel(string) bool {
+ return false
+}
+
+// HasErrorMessage returns true if the error contains the specified message.
+func (we WriteError) HasErrorMessage(message string) bool {
+ return strings.Contains(we.Message, message)
+}
+
+// HasErrorCodeWithMessage returns true if the error has the specified code and Message contains the specified message.
+func (we WriteError) HasErrorCodeWithMessage(code int, message string) bool {
+ return we.Code == code && strings.Contains(we.Message, message)
+}
+
+// serverError implements the ServerError interface.
+func (we WriteError) serverError() {}
+
+// WriteErrors is a group of write errors that occurred during execution of a write operation.
+type WriteErrors []WriteError
+
+// Error implements the error interface.
+func (we WriteErrors) Error() string {
+ errs := make([]error, len(we))
+ for i := 0; i < len(we); i++ {
+ errs[i] = we[i]
+ }
+ // WriteErrors isn't returned from batch operations, but we can still use the same formatter.
+ return "write errors: " + joinBatchErrors(errs)
+}
+
+func writeErrorsFromDriverWriteErrors(errs driver.WriteErrors) WriteErrors {
+ wes := make(WriteErrors, 0, len(errs))
+ for _, err := range errs {
+ wes = append(wes, WriteError{
+ Index: int(err.Index),
+ Code: int(err.Code),
+ Message: err.Message,
+ Details: bson.Raw(err.Details),
+ Raw: bson.Raw(err.Raw),
+ })
+ }
+ return wes
+}
+
+// WriteConcernError represents a write concern failure during execution of a write operation. This error type is only
+// returned as part of a WriteException or a BulkWriteException.
+type WriteConcernError struct {
+ Name string
+ Code int
+ Message string
+ Details bson.Raw
+ Raw bson.Raw // The original write concern error from the server response.
+}
+
+// Error implements the error interface.
+func (wce WriteConcernError) Error() string {
+ if wce.Name != "" {
+ return fmt.Sprintf("(%v) %v", wce.Name, wce.Message)
+ }
+ return wce.Message
+}
+
+// IsMaxTimeMSExpiredError returns true if the error is a MaxTimeMSExpired error.
+func (wce WriteConcernError) IsMaxTimeMSExpiredError() bool {
+ return wce.Code == 50
+}
+
+// WriteException is the error type returned by the InsertOne, DeleteOne, DeleteMany, UpdateOne, UpdateMany, and
+// ReplaceOne operations.
+type WriteException struct {
+ // The write concern error that occurred, or nil if there was none.
+ WriteConcernError *WriteConcernError
+
+ // The write errors that occurred during operation execution.
+ WriteErrors WriteErrors
+
+ // The categories to which the exception belongs.
+ Labels []string
+
+ // The original server response containing the error.
+ Raw bson.Raw
+}
+
+// Error implements the error interface.
+func (mwe WriteException) Error() string {
+ causes := make([]string, 0, 2)
+ if mwe.WriteConcernError != nil {
+ causes = append(causes, "write concern error: "+mwe.WriteConcernError.Error())
+ }
+ if len(mwe.WriteErrors) > 0 {
+ // The WriteErrors error message already starts with "write errors:", so don't add it to the
+ // error message again.
+ causes = append(causes, mwe.WriteErrors.Error())
+ }
+
+ message := "write exception: "
+ if len(causes) == 0 {
+ return message + "no causes"
+ }
+ return message + strings.Join(causes, ", ")
+}
+
+// HasErrorCode returns true if the error has the specified code.
+func (mwe WriteException) HasErrorCode(code int) bool {
+ return hasErrorCode(mwe, code)
+}
+
+// ErrorCodes returns a list of error codes returned by the server.
+func (mwe WriteException) ErrorCodes() []int {
+ errorCodes := []int{}
+ for _, writeError := range mwe.WriteErrors {
+ errorCodes = append(errorCodes, writeError.Code)
+ }
+
+ if mwe.WriteConcernError != nil {
+ errorCodes = append(errorCodes, mwe.WriteConcernError.Code)
+ }
+
+ return errorCodes
+}
+
+// HasErrorLabel returns true if the error contains the specified label.
+func (mwe WriteException) HasErrorLabel(label string) bool {
+ for _, l := range mwe.Labels {
+ if l == label {
+ return true
+ }
+ }
+ return false
+}
+
+// HasErrorMessage returns true if the error contains the specified message.
+func (mwe WriteException) HasErrorMessage(message string) bool {
+ if mwe.WriteConcernError != nil && strings.Contains(mwe.WriteConcernError.Message, message) {
+ return true
+ }
+ for _, we := range mwe.WriteErrors {
+ if strings.Contains(we.Message, message) {
+ return true
+ }
+ }
+ return false
+}
+
+// HasErrorCodeWithMessage returns true if any of the contained errors have the specified code and message.
+func (mwe WriteException) HasErrorCodeWithMessage(code int, message string) bool {
+ if mwe.WriteConcernError != nil &&
+ mwe.WriteConcernError.Code == code && strings.Contains(mwe.WriteConcernError.Message, message) {
+ return true
+ }
+ for _, we := range mwe.WriteErrors {
+ if we.Code == code && strings.Contains(we.Message, message) {
+ return true
+ }
+ }
+ return false
+}
+
+// serverError implements the ServerError interface.
+func (mwe WriteException) serverError() {}
+
+func convertDriverWriteConcernError(wce *driver.WriteConcernError) *WriteConcernError {
+ if wce == nil {
+ return nil
+ }
+
+ return &WriteConcernError{
+ Name: wce.Name,
+ Code: int(wce.Code),
+ Message: wce.Message,
+ Details: bson.Raw(wce.Details),
+ Raw: bson.Raw(wce.Raw),
+ }
+}
+
+// BulkWriteError is an error that occurred during execution of one operation in a BulkWrite. This error type is only
+// returned as part of a BulkWriteException.
+type BulkWriteError struct {
+ WriteError // The WriteError that occurred.
+ Request WriteModel // The WriteModel that caused this error.
+}
+
+// Error implements the error interface.
+func (bwe BulkWriteError) Error() string {
+ return bwe.WriteError.Error()
+}
+
+// BulkWriteException is the error type returned by BulkWrite and InsertMany operations.
+type BulkWriteException struct {
+ // The write concern error that occurred, or nil if there was none.
+ WriteConcernError *WriteConcernError
+
+ // The write errors that occurred during operation execution.
+ WriteErrors []BulkWriteError
+
+ // The categories to which the exception belongs.
+ Labels []string
+}
+
+// Error implements the error interface.
+func (bwe BulkWriteException) Error() string {
+ causes := make([]string, 0, 2)
+ if bwe.WriteConcernError != nil {
+ causes = append(causes, "write concern error: "+bwe.WriteConcernError.Error())
+ }
+ if len(bwe.WriteErrors) > 0 {
+ errs := make([]error, len(bwe.WriteErrors))
+ for i := 0; i < len(bwe.WriteErrors); i++ {
+ errs[i] = &bwe.WriteErrors[i]
+ }
+ causes = append(causes, "write errors: "+joinBatchErrors(errs))
+ }
+
+ message := "bulk write exception: "
+ if len(causes) == 0 {
+ return message + "no causes"
+ }
+ return "bulk write exception: " + strings.Join(causes, ", ")
+}
+
+// HasErrorCode returns true if any of the errors have the specified code.
+func (bwe BulkWriteException) HasErrorCode(code int) bool {
+ return hasErrorCode(bwe, code)
+}
+
+// ErrorCodes returns a list of error codes returned by the server.
+func (bwe BulkWriteException) ErrorCodes() []int {
+ errorCodes := []int{}
+ for _, writeError := range bwe.WriteErrors {
+ errorCodes = append(errorCodes, writeError.Code)
+ }
+
+ if bwe.WriteConcernError != nil {
+ errorCodes = append(errorCodes, bwe.WriteConcernError.Code)
+ }
+
+ return errorCodes
+}
+
+// HasErrorLabel returns true if the error contains the specified label.
+func (bwe BulkWriteException) HasErrorLabel(label string) bool {
+ for _, l := range bwe.Labels {
+ if l == label {
+ return true
+ }
+ }
+ return false
+}
+
+// HasErrorMessage returns true if the error contains the specified message.
+func (bwe BulkWriteException) HasErrorMessage(message string) bool {
+ if bwe.WriteConcernError != nil && strings.Contains(bwe.WriteConcernError.Message, message) {
+ return true
+ }
+ for _, we := range bwe.WriteErrors {
+ if strings.Contains(we.Message, message) {
+ return true
+ }
+ }
+ return false
+}
+
+// HasErrorCodeWithMessage returns true if any of the contained errors have the specified code and message.
+func (bwe BulkWriteException) HasErrorCodeWithMessage(code int, message string) bool {
+ if bwe.WriteConcernError != nil &&
+ bwe.WriteConcernError.Code == code && strings.Contains(bwe.WriteConcernError.Message, message) {
+ return true
+ }
+ for _, we := range bwe.WriteErrors {
+ if we.Code == code && strings.Contains(we.Message, message) {
+ return true
+ }
+ }
+ return false
+}
+
+// serverError implements the ServerError interface.
+func (bwe BulkWriteException) serverError() {}
+
+// ClientBulkWriteException is the error type returned by ClientBulkWrite operations.
+type ClientBulkWriteException struct {
+ // A top-level error that occurred when attempting to communicate with the server
+ // or execute the bulk write. This value may not be populated if the exception was
+ // thrown due to errors occurring on individual writes.
+ WriteError *WriteError
+
+ // The write concern errors that occurred.
+ WriteConcernErrors []WriteConcernError
+
+ // The write errors that occurred during individual operation execution.
+ // This map will contain at most one entry if the bulk write was ordered.
+ WriteErrors map[int]WriteError
+
+ // The results of any successful operations that were performed before the error
+ // was encountered.
+ PartialResult *ClientBulkWriteResult
+}
+
+// ErrorCodes returns a list of error codes returned by the server.
+func (bwe ClientBulkWriteException) ErrorCodes() []int {
+ codes := []int{}
+
+ if bwe.WriteError != nil {
+ codes = append(codes, bwe.WriteError.Code)
+ }
+ for _, wce := range bwe.WriteConcernErrors {
+ codes = append(codes, wce.Code)
+ }
+ for _, we := range bwe.WriteErrors {
+ codes = append(codes, we.Code)
+ }
+
+ return codes
+}
+
+// Error implements the error interface.
+func (bwe ClientBulkWriteException) Error() string {
+ causes := make([]string, 0, 4)
+ if bwe.WriteError != nil {
+ causes = append(causes, "top level error: "+bwe.WriteError.Error())
+ }
+ if len(bwe.WriteConcernErrors) > 0 {
+ errs := make([]error, len(bwe.WriteConcernErrors))
+ for i := 0; i < len(bwe.WriteConcernErrors); i++ {
+ errs[i] = bwe.WriteConcernErrors[i]
+ }
+ causes = append(causes, "write concern errors: "+joinBatchErrors(errs))
+ }
+ if len(bwe.WriteErrors) > 0 {
+ errs := make([]error, 0, len(bwe.WriteErrors))
+ for _, v := range bwe.WriteErrors {
+ errs = append(errs, v)
+ }
+ causes = append(causes, "write errors: "+joinBatchErrors(errs))
+ }
+ if bwe.PartialResult != nil {
+ causes = append(causes, fmt.Sprintf("result: %v", *bwe.PartialResult))
+ }
+
+ message := "bulk write exception: "
+ if len(causes) == 0 {
+ return message + "no causes"
+ }
+ return "bulk write exception: " + strings.Join(causes, ", ")
+}
+
+var _ LabeledError = timeoutError{}
+
+// timeoutError represents an error that occurred due to a timeout.
+type timeoutError struct {
+ Wrapped error
+}
+
+// Error implements the error interface.
+func (e timeoutError) Error() string {
+ const timeoutMsg = "operation timed out"
+ if e.Wrapped == nil {
+ return timeoutMsg
+ }
+ return fmt.Sprintf("%s: %v", timeoutMsg, e.Wrapped.Error())
+}
+
+// Unwrap returns the underlying error.
+func (e timeoutError) Unwrap() error {
+ return e.Wrapped
+}
+
+// HasErrorLabel returns true if the error contains the specified label.
+func (e timeoutError) HasErrorLabel(label string) bool {
+ if le := LabeledError(nil); errors.As(e.Wrapped, &le) {
+ return le.HasErrorLabel(label)
+ }
+ return false
+}
+
+// returnResult is used to determine if a function calling processWriteError should return
+// the result or return nil. Since the processWriteError function is used by many different
+// methods, both *One and *Many, we need a way to differentiate if the method should return
+// the result and the error.
+type returnResult int
+
+const (
+ rrNone returnResult = 1 << iota // None means do not return the result ever.
+ rrOne // One means return the result if this was called by a *One method.
+ rrMany // Many means return the result is this was called by a *Many method.
+ rrUnacknowledged
+
+ rrAll returnResult = rrOne | rrMany // All means always return the result.
+ rrAllUnacknowledged returnResult = rrAll | rrUnacknowledged // All + unacknowledged write
+)
+
+func (rr returnResult) isAcknowledged() bool {
+ return rr&rrUnacknowledged == 0
+}
+
+// processWriteError handles processing the result of a write operation. If the retrunResult matches
+// the calling method's type, it should return the result object in addition to the error.
+// This function will wrap the errors from other packages and return them as errors from this package.
+//
+// WriteConcernError will be returned over WriteErrors if both are present.
+func processWriteError(err error) (returnResult, error) {
+ if err == nil {
+ return rrAll, nil
+ }
+ // Do not propagate the acknowledgement sentinel error. For DDL commands,
+ // (creating indexes, dropping collections, etc) acknowledgement should be
+ // ignored. For non-DDL write commands (insert, update, etc), acknowledgement
+ // should be be propagated at the result-level: e.g.,
+ // SingleResult.Acknowledged.
+ if errors.Is(err, driver.ErrUnacknowledgedWrite) {
+ return rrAllUnacknowledged, nil
+ }
+
+ var wce driver.WriteCommandError
+ if !errors.As(err, &wce) {
+ return rrNone, wrapErrors(err)
+ }
+
+ return rrMany, WriteException{
+ WriteConcernError: convertDriverWriteConcernError(wce.WriteConcernError),
+ WriteErrors: writeErrorsFromDriverWriteErrors(wce.WriteErrors),
+ Labels: wce.Labels,
+ Raw: bson.Raw(wce.Raw),
+ }
+}
+
+// batchErrorsTargetLength is the target length of error messages returned by batch operation
+// error types. Try to limit batch error messages to 2kb to prevent problems when printing error
+// messages from large batch operations.
+const batchErrorsTargetLength = 2000
+
+// joinBatchErrors appends messages from the given errors to a comma-separated string. If the
+// string exceeds 2kb, it stops appending error messages and appends the message "+N more errors..."
+// to the end.
+//
+// Example format:
+//
+// "[message 1, message 2, +8 more errors...]"
+func joinBatchErrors(errs []error) string {
+ var buf bytes.Buffer
+ fmt.Fprint(&buf, "[")
+ for idx, err := range errs {
+ if idx != 0 {
+ fmt.Fprint(&buf, ", ")
+ }
+ // If the error message has exceeded the target error message length, stop appending errors
+ // to the message and append the number of remaining errors instead.
+ if buf.Len() > batchErrorsTargetLength {
+ fmt.Fprintf(&buf, "+%d more errors...", len(errs)-idx)
+ break
+ }
+ fmt.Fprint(&buf, err.Error())
+ }
+ fmt.Fprint(&buf, "]")
+
+ return buf.String()
+}
+
+// ErrorCodes returns the list of server error codes contained in err.
+func ErrorCodes(err error) []int {
+ if err == nil {
+ return nil
+ }
+
+ var ec errorCoder
+ // First check if the error is already wrapped (common case)
+ if errors.As(err, &ec) {
+ return ec.ErrorCodes()
+ }
+
+ // Only wrap if necessary (for internal errors)
+ if errors.As(wrapErrors(err), &ec) {
+ return ec.ErrorCodes()
+ }
+
+ return []int{}
+}
diff --git a/vendor/go.mongodb.org/mongo-driver/v2/mongo/gridfs_bucket.go b/vendor/go.mongodb.org/mongo-driver/v2/mongo/gridfs_bucket.go
new file mode 100644
index 0000000..7423077
--- /dev/null
+++ b/vendor/go.mongodb.org/mongo-driver/v2/mongo/gridfs_bucket.go
@@ -0,0 +1,585 @@
+// Copyright (C) MongoDB, Inc. 2017-present.
+//
+// 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
+
+package mongo
+
+import (
+ "bytes"
+ "context"
+ "errors"
+ "fmt"
+ "io"
+
+ "go.mongodb.org/mongo-driver/v2/bson"
+ "go.mongodb.org/mongo-driver/v2/internal/csot"
+ "go.mongodb.org/mongo-driver/v2/internal/mongoutil"
+ "go.mongodb.org/mongo-driver/v2/mongo/options"
+ "go.mongodb.org/mongo-driver/v2/mongo/readconcern"
+ "go.mongodb.org/mongo-driver/v2/mongo/readpref"
+ "go.mongodb.org/mongo-driver/v2/mongo/writeconcern"
+ "go.mongodb.org/mongo-driver/v2/x/bsonx/bsoncore"
+)
+
+// TODO: add sessions options
+
+// DefaultGridFSChunkSize is the default size of each file chunk.
+const DefaultGridFSChunkSize int32 = 255 * 1024 // 255 KiB
+
+// ErrFileNotFound occurs if a user asks to download a file with a file ID that isn't found in the files collection.
+var ErrFileNotFound = errors.New("file with given parameters not found")
+
+// ErrMissingGridFSChunkSize occurs when downloading a file if the files
+// collection document is missing the "chunkSize" field.
+var ErrMissingGridFSChunkSize = errors.New("files collection document does not contain a 'chunkSize' field")
+
+// GridFSBucket represents a GridFS bucket.
+type GridFSBucket struct {
+ db *Database
+ chunksColl *Collection // collection to store file chunks
+ filesColl *Collection // collection to store file metadata
+
+ name string
+ chunkSize int32
+ wc *writeconcern.WriteConcern
+ rc *readconcern.ReadConcern
+ rp *readpref.ReadPref
+
+ firstWriteDone bool
+ readBuf []byte
+ writeBuf []byte
+}
+
+// upload contains options to upload a file to a bucket.
+type upload struct {
+ chunkSize int32
+ metadata bson.D
+}
+
+// OpenUploadStream creates a file ID new upload stream for a file given the
+// filename.
+//
+// The context provided to this method controls the entire lifetime of an
+// upload stream io.Writer. If the context does set a deadline, then the
+// client-level timeout will be used to cap the lifetime of the stream.
+func (b *GridFSBucket) OpenUploadStream(
+ ctx context.Context,
+ filename string,
+ opts ...options.Lister[options.GridFSUploadOptions],
+) (*GridFSUploadStream, error) {
+ return b.OpenUploadStreamWithID(ctx, bson.NewObjectID(), filename, opts...)
+}
+
+// OpenUploadStreamWithID creates a new upload stream for a file given the file
+// ID and filename.
+//
+// The context provided to this method controls the entire lifetime of an
+// upload stream io.Writer. If the context does set a deadline, then the
+// client-level timeout will be used to cap the lifetime of the stream.
+func (b *GridFSBucket) OpenUploadStreamWithID(
+ ctx context.Context,
+ fileID any,
+ filename string,
+ opts ...options.Lister[options.GridFSUploadOptions],
+) (*GridFSUploadStream, error) {
+ ctx, cancel := csot.WithTimeout(ctx, b.db.client.timeout)
+
+ if err := b.checkFirstWrite(ctx); err != nil {
+ return nil, err
+ }
+
+ upload, err := b.parseGridFSUploadOptions(opts...)
+ if err != nil {
+ return nil, err
+ }
+
+ return newUploadStream(ctx, cancel, upload, fileID, filename, b.chunksColl, b.filesColl), nil
+}
+
+// UploadFromStream creates a fileID and uploads a file given a source stream.
+//
+// If this upload requires a custom write deadline to be set on the bucket, it
+// cannot be done concurrently with other write operations operations on this
+// bucket that also require a custom deadline.
+//
+// The context provided to this method controls the entire lifetime of an
+// upload stream io.Writer. If the context does set a deadline, then the
+// client-level timeout will be used to cap the lifetime of the stream.
+func (b *GridFSBucket) UploadFromStream(
+ ctx context.Context,
+ filename string,
+ source io.Reader,
+ opts ...options.Lister[options.GridFSUploadOptions],
+) (bson.ObjectID, error) {
+ fileID := bson.NewObjectID()
+ err := b.UploadFromStreamWithID(ctx, fileID, filename, source, opts...)
+ return fileID, err
+}
+
+// UploadFromStreamWithID uploads a file given a source stream.
+//
+// If this upload requires a custom write deadline to be set on the bucket, it
+// cannot be done concurrently with other write operations operations on this
+// bucket that also require a custom deadline.
+//
+// The context provided to this method controls the entire lifetime of an
+// upload stream io.Writer. If the context does set a deadline, then the
+// client-level timeout will be used to cap the lifetime of the stream.
+func (b *GridFSBucket) UploadFromStreamWithID(
+ ctx context.Context,
+ fileID any,
+ filename string,
+ source io.Reader,
+ opts ...options.Lister[options.GridFSUploadOptions],
+) error {
+ ctx, cancel := csot.WithTimeout(ctx, b.db.client.timeout)
+ defer cancel()
+
+ us, err := b.OpenUploadStreamWithID(ctx, fileID, filename, opts...)
+ if err != nil {
+ return err
+ }
+
+ for {
+ n, err := source.Read(b.readBuf)
+ if err != nil && err != io.EOF {
+ _ = us.Abort() // upload considered aborted if source stream returns an error
+ return err
+ }
+
+ if n > 0 {
+ _, err := us.Write(b.readBuf[:n])
+ if err != nil {
+ return err
+ }
+ }
+
+ if n == 0 || err == io.EOF {
+ break
+ }
+ }
+
+ return us.Close()
+}
+
+// OpenDownloadStream creates a stream from which the contents of the file can
+// be read.
+//
+// The context provided to this method controls the entire lifetime of an
+// upload stream io.Writer. If the context does set a deadline, then the
+// client-level timeout will be used to cap the lifetime of the stream.
+func (b *GridFSBucket) OpenDownloadStream(ctx context.Context, fileID any) (*GridFSDownloadStream, error) {
+ return b.openDownloadStream(ctx, bson.D{{"_id", fileID}})
+}
+
+// DownloadToStream downloads the file with the specified fileID and writes it
+// to the provided io.Writer. Returns the number of bytes written to the stream
+// and an error, or nil if there was no error.
+//
+// If this download requires a custom read deadline to be set on the bucket, it
+// cannot be done concurrently with other read operations operations on this
+// bucket that also require a custom deadline.
+//
+// The context provided to this method controls the entire lifetime of an
+// upload stream io.Writer. If the context does set a deadline, then the
+// client-level timeout will be used to cap the lifetime of the stream.
+func (b *GridFSBucket) DownloadToStream(ctx context.Context, fileID any, stream io.Writer) (int64, error) {
+ ds, err := b.OpenDownloadStream(ctx, fileID)
+ if err != nil {
+ return 0, err
+ }
+
+ return b.downloadToStream(ds, stream)
+}
+
+// OpenDownloadStreamByName opens a download stream for the file with the given
+// filename.
+//
+// The context provided to this method controls the entire lifetime of an
+// upload stream io.Writer. If the context does set a deadline, then the
+// client-level timeout will be used to cap the lifetime of the stream.
+func (b *GridFSBucket) OpenDownloadStreamByName(
+ ctx context.Context,
+ filename string,
+ opts ...options.Lister[options.GridFSNameOptions],
+) (*GridFSDownloadStream, error) {
+ args, err := mongoutil.NewOptions[options.GridFSNameOptions](opts...)
+ if err != nil {
+ return nil, fmt.Errorf("failed to construct options from builder: %w", err)
+ }
+
+ numSkip := options.DefaultRevision
+ if args.Revision != nil {
+ numSkip = *args.Revision
+ }
+
+ var sortOrder int32 = 1
+
+ if numSkip < 0 {
+ sortOrder = -1
+ numSkip = (-1 * numSkip) - 1
+ }
+
+ findOpts := options.FindOne().SetSkip(int64(numSkip)).SetSort(bson.D{{"uploadDate", sortOrder}})
+
+ return b.openDownloadStream(ctx, bson.D{{"filename", filename}}, findOpts)
+}
+
+// DownloadToStreamByName downloads the file with the given name to the given
+// io.Writer.
+//
+// If this download requires a custom read deadline to be set on the bucket, it
+// cannot be done concurrently with other read operations operations on this
+// bucket that also require a custom deadline.
+//
+// The context provided to this method controls the entire lifetime of an
+// upload stream io.Writer. If the context does set a deadline, then the
+// client-level timeout will be used to cap the lifetime of the stream.
+func (b *GridFSBucket) DownloadToStreamByName(
+ ctx context.Context,
+ filename string,
+ stream io.Writer,
+ opts ...options.Lister[options.GridFSNameOptions],
+) (int64, error) {
+ ds, err := b.OpenDownloadStreamByName(ctx, filename, opts...)
+ if err != nil {
+ return 0, err
+ }
+
+ return b.downloadToStream(ds, stream)
+}
+
+// Delete deletes all chunks and metadata associated with the file with the
+// given file ID and runs the underlying delete operations with the provided
+// context.
+func (b *GridFSBucket) Delete(ctx context.Context, fileID any) error {
+ ctx, cancel := csot.WithTimeout(ctx, b.db.client.timeout)
+ defer cancel()
+
+ res, err := b.filesColl.DeleteOne(ctx, bson.D{{"_id", fileID}})
+ if err == nil && res.DeletedCount == 0 {
+ err = ErrFileNotFound
+ }
+ if err != nil {
+ _ = b.deleteChunks(ctx, fileID) // Can attempt to delete chunks even if no docs in files collection matched.
+ return err
+ }
+
+ return b.deleteChunks(ctx, fileID)
+}
+
+// Find returns the files collection documents that match the given filter and
+// runs the underlying find query with the provided context.
+func (b *GridFSBucket) Find(
+ ctx context.Context,
+ filter any,
+ opts ...options.Lister[options.GridFSFindOptions],
+) (*Cursor, error) {
+ args, err := mongoutil.NewOptions[options.GridFSFindOptions](opts...)
+ if err != nil {
+ return nil, fmt.Errorf("failed to construct options from builder: %w", err)
+ }
+
+ find := options.Find()
+ if args.AllowDiskUse != nil {
+ find.SetAllowDiskUse(*args.AllowDiskUse)
+ }
+ if args.BatchSize != nil {
+ find.SetBatchSize(*args.BatchSize)
+ }
+ if args.Limit != nil {
+ find.SetLimit(int64(*args.Limit))
+ }
+ if args.NoCursorTimeout != nil {
+ find.SetNoCursorTimeout(*args.NoCursorTimeout)
+ }
+ if args.Skip != nil {
+ find.SetSkip(int64(*args.Skip))
+ }
+ if args.Sort != nil {
+ find.SetSort(args.Sort)
+ }
+
+ return b.filesColl.Find(ctx, filter, find)
+}
+
+// Rename renames the stored file with the specified file ID.
+func (b *GridFSBucket) Rename(ctx context.Context, fileID any, newFilename string) error {
+ res, err := b.filesColl.UpdateOne(ctx,
+ bson.D{{"_id", fileID}},
+ bson.D{{"$set", bson.D{{"filename", newFilename}}}},
+ )
+ if err != nil {
+ return err
+ }
+
+ if res.MatchedCount == 0 {
+ return ErrFileNotFound
+ }
+
+ return nil
+}
+
+// Drop drops the files and chunks collections associated with this bucket and
+// runs the drop operations with the provided context.
+func (b *GridFSBucket) Drop(ctx context.Context) error {
+ ctx, cancel := csot.WithTimeout(ctx, b.db.client.timeout)
+ defer cancel()
+
+ err := b.filesColl.Drop(ctx)
+ if err != nil {
+ return err
+ }
+
+ return b.chunksColl.Drop(ctx)
+}
+
+// GetFilesCollection returns a handle to the collection that stores the file documents for this bucket.
+func (b *GridFSBucket) GetFilesCollection() *Collection {
+ return b.filesColl
+}
+
+// GetChunksCollection returns a handle to the collection that stores the file chunks for this bucket.
+func (b *GridFSBucket) GetChunksCollection() *Collection {
+ return b.chunksColl
+}
+
+func (b *GridFSBucket) openDownloadStream(
+ ctx context.Context,
+ filter any,
+ opts ...options.Lister[options.FindOneOptions],
+) (*GridFSDownloadStream, error) {
+ ctx, cancel := csot.WithTimeout(ctx, b.db.client.timeout)
+
+ result := b.filesColl.FindOne(ctx, filter, opts...)
+
+ // Unmarshal the data into a File instance, which can be passed to newGridFSDownloadStream. The _id value has to be
+ // parsed out separately because "_id" will not match the File.ID field and we want to avoid exposing BSON tags
+ // in the File type. After parsing it, use RawValue.Unmarshal to ensure File.ID is set to the appropriate value.
+ var resp findFileResponse
+ if err := result.Decode(&resp); err != nil {
+ if errors.Is(err, ErrNoDocuments) {
+ return nil, ErrFileNotFound
+ }
+
+ return nil, fmt.Errorf("error decoding files collection document: %w", err)
+ }
+
+ foundFile := newFileFromResponse(resp)
+
+ if foundFile.Length == 0 {
+ return newGridFSDownloadStream(ctx, cancel, nil, foundFile.ChunkSize, foundFile), nil
+ }
+
+ // For a file with non-zero length, chunkSize must exist so we know what size to expect when downloading chunks.
+ if foundFile.ChunkSize == 0 {
+ return nil, ErrMissingGridFSChunkSize
+ }
+
+ chunksCursor, err := b.findChunks(ctx, foundFile.ID)
+ if err != nil {
+ return nil, err
+ }
+
+ // The chunk size can be overridden for individual files, so the expected chunk size should be the "chunkSize"
+ // field from the files collection document, not the bucket's chunk size.
+ return newGridFSDownloadStream(ctx, cancel, chunksCursor, foundFile.ChunkSize, foundFile), nil
+}
+
+func (b *GridFSBucket) downloadToStream(ds *GridFSDownloadStream, stream io.Writer) (int64, error) {
+ copied, err := io.Copy(stream, ds)
+ if err != nil {
+ _ = ds.Close()
+ return 0, err
+ }
+
+ return copied, ds.Close()
+}
+
+func (b *GridFSBucket) deleteChunks(ctx context.Context, fileID any) error {
+ _, err := b.chunksColl.DeleteMany(ctx, bson.D{{"files_id", fileID}})
+ return err
+}
+
+func (b *GridFSBucket) findChunks(ctx context.Context, fileID any) (*Cursor, error) {
+ chunksCursor, err := b.chunksColl.Find(ctx,
+ bson.D{{"files_id", fileID}},
+ options.Find().SetSort(bson.D{{"n", 1}})) // sort by chunk index
+ if err != nil {
+ return nil, err
+ }
+
+ return chunksCursor, nil
+}
+
+// returns true if the 2 index documents are equal
+func numericalIndexDocsEqual(expected, actual bsoncore.Document) (bool, error) {
+ if bytes.Equal(expected, actual) {
+ return true, nil
+ }
+
+ actualElems, err := actual.Elements()
+ if err != nil {
+ return false, err
+ }
+ expectedElems, err := expected.Elements()
+ if err != nil {
+ return false, err
+ }
+
+ if len(actualElems) != len(expectedElems) {
+ return false, nil
+ }
+
+ for idx, expectedElem := range expectedElems {
+ actualElem := actualElems[idx]
+ if actualElem.Key() != expectedElem.Key() {
+ return false, nil
+ }
+
+ actualVal := actualElem.Value()
+ expectedVal := expectedElem.Value()
+ actualInt, actualOK := actualVal.AsInt64OK()
+ expectedInt, expectedOK := expectedVal.AsInt64OK()
+
+ // GridFS indexes always have numeric values
+ if !actualOK || !expectedOK {
+ return false, nil
+ }
+
+ if actualInt != expectedInt {
+ return false, nil
+ }
+ }
+ return true, nil
+}
+
+// Create an index if it doesn't already exist
+func createNumericalIndexIfNotExists(ctx context.Context, iv IndexView, model IndexModel) error {
+ c, err := iv.List(ctx)
+ if err != nil {
+ return err
+ }
+ defer func() {
+ _ = c.Close(ctx)
+ }()
+
+ modelKeysBytes, err := bson.Marshal(model.Keys)
+ if err != nil {
+ return err
+ }
+ modelKeysDoc := bsoncore.Document(modelKeysBytes)
+
+ for c.Next(ctx) {
+ keyElem, err := c.Current.LookupErr("key")
+ if err != nil {
+ return err
+ }
+
+ keyElemDoc := keyElem.Document()
+
+ found, err := numericalIndexDocsEqual(modelKeysDoc, bsoncore.Document(keyElemDoc))
+ if err != nil {
+ return err
+ }
+ if found {
+ return nil
+ }
+ }
+
+ _, err = iv.CreateOne(ctx, model)
+ return err
+}
+
+// create indexes on the files and chunks collection if needed
+func (b *GridFSBucket) createIndexes(ctx context.Context) error {
+ // must use primary read pref mode to check if files coll empty
+ cloned := b.filesColl.Clone(options.Collection().SetReadPreference(readpref.Primary()))
+
+ docRes := cloned.FindOne(ctx, bson.D{}, options.FindOne().SetProjection(bson.D{{"_id", 1}}))
+
+ _, err := docRes.Raw()
+ if !errors.Is(err, ErrNoDocuments) {
+ // nil, or error that occurred during the FindOne operation
+ return err
+ }
+
+ filesIv := b.filesColl.Indexes()
+ chunksIv := b.chunksColl.Indexes()
+
+ filesModel := IndexModel{
+ Keys: bson.D{
+ {"filename", int32(1)},
+ {"uploadDate", int32(1)},
+ },
+ }
+
+ chunksModel := IndexModel{
+ Keys: bson.D{
+ {"files_id", int32(1)},
+ {"n", int32(1)},
+ },
+ Options: options.Index().SetUnique(true),
+ }
+
+ if err = createNumericalIndexIfNotExists(ctx, filesIv, filesModel); err != nil {
+ return err
+ }
+ return createNumericalIndexIfNotExists(ctx, chunksIv, chunksModel)
+}
+
+func (b *GridFSBucket) checkFirstWrite(ctx context.Context) error {
+ if !b.firstWriteDone {
+ // before the first write operation, must determine if files collection is empty
+ // if so, create indexes if they do not already exist
+
+ if err := b.createIndexes(ctx); err != nil {
+ return err
+ }
+ b.firstWriteDone = true
+ }
+
+ return nil
+}
+
+func (b *GridFSBucket) parseGridFSUploadOptions(opts ...options.Lister[options.GridFSUploadOptions]) (*upload, error) {
+ upload := &upload{
+ chunkSize: b.chunkSize, // upload chunk size defaults to bucket's value
+ }
+
+ args, err := mongoutil.NewOptions[options.GridFSUploadOptions](opts...)
+ if err != nil {
+ return nil, fmt.Errorf("failed to construct options from builder: %w", err)
+ }
+
+ if args.ChunkSizeBytes != nil {
+ upload.chunkSize = *args.ChunkSizeBytes
+ }
+ if args.Registry == nil {
+ args.Registry = defaultRegistry
+ }
+ if args.Metadata != nil {
+ // TODO(GODRIVER-2726): Replace with marshal() and unmarshal() once the
+ // TODO gridfs package is merged into the mongo package.
+ buf := new(bytes.Buffer)
+ vw := bson.NewDocumentWriter(buf)
+ enc := bson.NewEncoder(vw)
+ enc.SetRegistry(args.Registry)
+ err := enc.Encode(args.Metadata)
+ if err != nil {
+ return nil, err
+ }
+ var doc bson.D
+ dec := bson.NewDecoder(bson.NewDocumentReader(bytes.NewReader(buf.Bytes())))
+ dec.SetRegistry(args.Registry)
+ unMarErr := dec.Decode(&doc)
+ if unMarErr != nil {
+ return nil, unMarErr
+ }
+ upload.metadata = doc
+ }
+
+ return upload, nil
+}
diff --git a/vendor/go.mongodb.org/mongo-driver/v2/mongo/gridfs_download_stream.go b/vendor/go.mongodb.org/mongo-driver/v2/mongo/gridfs_download_stream.go
new file mode 100644
index 0000000..c7967b7
--- /dev/null
+++ b/vendor/go.mongodb.org/mongo-driver/v2/mongo/gridfs_download_stream.go
@@ -0,0 +1,299 @@
+// Copyright (C) MongoDB, Inc. 2017-present.
+//
+// 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
+
+package mongo
+
+import (
+ "context"
+ "errors"
+ "io"
+ "math"
+ "time"
+
+ "go.mongodb.org/mongo-driver/v2/bson"
+)
+
+// ErrMissingChunk indicates that the number of chunks read from the server is
+// less than expected. This error is specific to GridFS operations.
+var ErrMissingChunk = errors.New("EOF missing one or more chunks")
+
+// ErrWrongSize is used when the chunk retrieved from the server does not have
+// the expected size. This error is specific to GridFS operations.
+var ErrWrongSize = errors.New("chunk size does not match expected size")
+
+var errNoMoreChunks = errors.New("no more chunks remaining")
+
+// GridFSDownloadStream is a io.Reader that can be used to download a file from a GridFS bucket.
+type GridFSDownloadStream struct {
+ numChunks int32
+ chunkSize int32
+ cursor *Cursor
+ done bool
+ closed bool
+ buffer []byte // store up to 1 chunk if the user provided buffer isn't big enough
+ bufferStart int
+ bufferEnd int
+ expectedChunk int32 // index of next expected chunk
+ fileLen int64
+ ctx context.Context
+ cancel context.CancelFunc
+
+ // The pointer returned by GetFile. This should not be used in the actual GridFSDownloadStream code outside of the
+ // newGridFSDownloadStream constructor because the values can be mutated by the user after calling GetFile. Instead,
+ // any values needed in the code should be stored separately and copied over in the constructor.
+ file *GridFSFile
+}
+
+// GridFSFile represents a file stored in GridFS. This type can be used to
+// access file information when downloading using the
+// GridFSDownloadStream.GetFile method.
+type GridFSFile struct {
+ // ID is the file's ID. This will match the file ID specified when uploading the file. If an upload helper that
+ // does not require a file ID was used, this field will be a bson.ObjectID.
+ ID any
+
+ // Length is the length of this file in bytes.
+ Length int64
+
+ // ChunkSize is the maximum number of bytes for each chunk in this file.
+ ChunkSize int32
+
+ // UploadDate is the time this file was added to GridFS in UTC. This field is set by the driver and is not configurable.
+ // The Metadata field can be used to store a custom date.
+ UploadDate time.Time
+
+ // Name is the name of this file.
+ Name string
+
+ // Metadata is additional data that was specified when creating this file. This field can be unmarshalled into a
+ // custom type using the bson.Unmarshal family of functions.
+ Metadata bson.Raw
+}
+
+var _ bson.Unmarshaler = &GridFSFile{}
+
+// findFileResponse is a temporary type used to unmarshal documents from the
+// files collection and can be transformed into a File instance. This type
+// exists to avoid adding BSON struct tags to the exported File type.
+type findFileResponse struct {
+ ID any `bson:"_id"`
+ Length int64 `bson:"length"`
+ ChunkSize int32 `bson:"chunkSize"`
+ UploadDate time.Time `bson:"uploadDate"`
+ Name string `bson:"filename"`
+ Metadata bson.Raw `bson:"metadata"`
+}
+
+func newFileFromResponse(resp findFileResponse) *GridFSFile {
+ return &GridFSFile{
+ ID: resp.ID,
+ Length: resp.Length,
+ ChunkSize: resp.ChunkSize,
+ UploadDate: resp.UploadDate,
+ Name: resp.Name,
+ Metadata: resp.Metadata,
+ }
+}
+
+// UnmarshalBSON implements the bson.Unmarshaler interface.
+func (f *GridFSFile) UnmarshalBSON(data []byte) error {
+ var temp findFileResponse
+ if err := bson.Unmarshal(data, &temp); err != nil {
+ return err
+ }
+
+ f.ID = temp.ID
+ f.Length = temp.Length
+ f.ChunkSize = temp.ChunkSize
+ f.UploadDate = temp.UploadDate
+ f.Name = temp.Name
+ f.Metadata = temp.Metadata
+
+ return nil
+}
+
+func newGridFSDownloadStream(
+ ctx context.Context,
+ cancel context.CancelFunc,
+ cursor *Cursor,
+ chunkSize int32,
+ file *GridFSFile,
+) *GridFSDownloadStream {
+ numChunks := int32(math.Ceil(float64(file.Length) / float64(chunkSize)))
+
+ return &GridFSDownloadStream{
+ numChunks: numChunks,
+ chunkSize: chunkSize,
+ cursor: cursor,
+ buffer: make([]byte, chunkSize),
+ done: cursor == nil,
+ fileLen: file.Length,
+ file: file,
+ ctx: ctx,
+ cancel: cancel,
+ }
+}
+
+// Close closes this download stream.
+func (ds *GridFSDownloadStream) Close() error {
+ defer func() {
+ if ds.cancel != nil {
+ ds.cancel()
+ }
+ }()
+
+ if ds.closed {
+ return ErrStreamClosed
+ }
+
+ ds.closed = true
+ if ds.cursor != nil {
+ return ds.cursor.Close(context.Background())
+ }
+ return nil
+}
+
+// Read reads the file from the server and writes it to a destination byte slice.
+func (ds *GridFSDownloadStream) Read(p []byte) (int, error) {
+ if ds.closed {
+ return 0, ErrStreamClosed
+ }
+
+ if ds.done {
+ return 0, io.EOF
+ }
+
+ bytesCopied := 0
+ var err error
+ for bytesCopied < len(p) {
+ if ds.bufferStart >= ds.bufferEnd {
+ // Buffer is empty and can load in data from new chunk.
+ err = ds.fillBuffer(ds.ctx)
+ if err != nil {
+ if errors.Is(err, errNoMoreChunks) {
+ if bytesCopied == 0 {
+ ds.done = true
+ return 0, io.EOF
+ }
+ return bytesCopied, nil
+ }
+ return bytesCopied, err
+ }
+ }
+
+ copied := copy(p[bytesCopied:], ds.buffer[ds.bufferStart:ds.bufferEnd])
+
+ bytesCopied += copied
+ ds.bufferStart += copied
+ }
+
+ return len(p), nil
+}
+
+// Skip skips a given number of bytes in the file.
+func (ds *GridFSDownloadStream) Skip(skip int64) (int64, error) {
+ if ds.closed {
+ return 0, ErrStreamClosed
+ }
+
+ if ds.done {
+ return 0, nil
+ }
+
+ var skipped int64
+ var err error
+
+ for skipped < skip {
+ if ds.bufferStart >= ds.bufferEnd {
+ // Buffer is empty and can load in data from new chunk.
+ err = ds.fillBuffer(ds.ctx)
+ if err != nil {
+ if errors.Is(err, errNoMoreChunks) {
+ return skipped, nil
+ }
+ return skipped, err
+ }
+ }
+
+ toSkip := skip - skipped
+ // Cap the amount to skip to the remaining bytes in the buffer to be consumed.
+ bufferRemaining := ds.bufferEnd - ds.bufferStart
+ if toSkip > int64(bufferRemaining) {
+ toSkip = int64(bufferRemaining)
+ }
+
+ skipped += toSkip
+ ds.bufferStart += int(toSkip)
+ }
+
+ return skip, nil
+}
+
+// GetFile returns a File object representing the file being downloaded.
+func (ds *GridFSDownloadStream) GetFile() *GridFSFile {
+ return ds.file
+}
+
+func (ds *GridFSDownloadStream) fillBuffer(ctx context.Context) error {
+ if !ds.cursor.Next(ctx) {
+ ds.done = true
+ // Check for cursor error, otherwise there are no more chunks.
+ if ds.cursor.Err() != nil {
+ _ = ds.cursor.Close(ctx)
+ return ds.cursor.Err()
+ }
+ // If there are no more chunks, but we didn't read the expected number of chunks, return an
+ // ErrMissingChunk error to indicate that we're missing chunks at the end of the file.
+ if ds.expectedChunk != ds.numChunks {
+ return ErrMissingChunk
+ }
+ return errNoMoreChunks
+ }
+
+ chunkIndex, err := ds.cursor.Current.LookupErr("n")
+ if err != nil {
+ return err
+ }
+
+ var chunkIndexInt32 int32
+ if chunkIndexInt64, ok := chunkIndex.Int64OK(); ok {
+ chunkIndexInt32 = int32(chunkIndexInt64)
+ } else {
+ chunkIndexInt32 = chunkIndex.Int32()
+ }
+
+ if chunkIndexInt32 != ds.expectedChunk {
+ return ErrMissingChunk
+ }
+
+ ds.expectedChunk++
+ data, err := ds.cursor.Current.LookupErr("data")
+ if err != nil {
+ return err
+ }
+
+ _, dataBytes := data.Binary()
+ copied := copy(ds.buffer, dataBytes)
+
+ bytesLen := int32(len(dataBytes))
+ if ds.expectedChunk == ds.numChunks {
+ // final chunk can be fewer than ds.chunkSize bytes
+ bytesDownloaded := int64(ds.chunkSize) * (int64(ds.expectedChunk) - int64(1))
+ bytesRemaining := ds.fileLen - bytesDownloaded
+
+ if int64(bytesLen) != bytesRemaining {
+ return ErrWrongSize
+ }
+ } else if bytesLen != ds.chunkSize {
+ // all intermediate chunks must have size ds.chunkSize
+ return ErrWrongSize
+ }
+
+ ds.bufferStart = 0
+ ds.bufferEnd = copied
+
+ return nil
+}
diff --git a/vendor/go.mongodb.org/mongo-driver/v2/mongo/gridfs_upload_stream.go b/vendor/go.mongodb.org/mongo-driver/v2/mongo/gridfs_upload_stream.go
new file mode 100644
index 0000000..8902270
--- /dev/null
+++ b/vendor/go.mongodb.org/mongo-driver/v2/mongo/gridfs_upload_stream.go
@@ -0,0 +1,204 @@
+// Copyright (C) MongoDB, Inc. 2017-present.
+//
+// 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
+
+package mongo
+
+import (
+ "context"
+ "errors"
+ "math"
+ "time"
+
+ "go.mongodb.org/mongo-driver/v2/bson"
+)
+
+// uploadBufferSize is the size in bytes of one stream batch. Chunks will be written to the db after the sum of chunk
+// lengths is equal to the batch size.
+const uploadBufferSize = 16 * 1024 * 1024 // 16 MiB
+
+// ErrStreamClosed is an error returned if an operation is attempted on a closed/aborted stream.
+var ErrStreamClosed = errors.New("stream is closed or aborted")
+
+// GridFSUploadStream is used to upload a file in chunks. This type implements the io.Writer interface and a file can be
+// uploaded using the Write method. After an upload is complete, the Close method must be called to write file
+// metadata.
+type GridFSUploadStream struct {
+ *upload // chunk size and metadata
+ FileID any
+
+ chunkIndex int
+ chunksColl *Collection // collection to store file chunks
+ filename string
+ filesColl *Collection // collection to store file metadata
+ closed bool
+ buffer []byte
+ bufferIndex int
+ fileLen int64
+ ctx context.Context
+ cancel context.CancelFunc
+}
+
+// NewUploadStream creates a new upload stream.
+func newUploadStream(
+ ctx context.Context,
+ cancel context.CancelFunc,
+ up *upload,
+ fileID any,
+ filename string,
+ chunks, files *Collection,
+) *GridFSUploadStream {
+ return &GridFSUploadStream{
+ upload: up,
+ FileID: fileID,
+
+ chunksColl: chunks,
+ filename: filename,
+ filesColl: files,
+ buffer: make([]byte, uploadBufferSize),
+ ctx: ctx,
+ cancel: cancel,
+ }
+}
+
+// Close writes file metadata to the files collection and cleans up any resources associated with the UploadStream.
+func (us *GridFSUploadStream) Close() error {
+ defer func() {
+ if us.cancel != nil {
+ us.cancel()
+ }
+ }()
+
+ if us.closed {
+ return ErrStreamClosed
+ }
+
+ if us.bufferIndex != 0 {
+ if err := us.uploadChunks(us.ctx, true); err != nil {
+ return err
+ }
+ }
+
+ if err := us.createFilesCollDoc(us.ctx); err != nil {
+ return err
+ }
+
+ us.closed = true
+ return nil
+}
+
+// Write transfers the contents of a byte slice into this upload stream. If the stream's underlying buffer fills up,
+// the buffer will be uploaded as chunks to the server. Implements the io.Writer interface.
+func (us *GridFSUploadStream) Write(p []byte) (int, error) {
+ if us.closed {
+ return 0, ErrStreamClosed
+ }
+
+ origLen := len(p)
+ for len(p) != 0 {
+
+ n := copy(us.buffer[us.bufferIndex:], p) // copy as much as possible
+ p = p[n:]
+ us.bufferIndex += n
+
+ if us.bufferIndex == uploadBufferSize {
+ err := us.uploadChunks(us.ctx, false)
+ if err != nil {
+ return 0, err
+ }
+ }
+ }
+ return origLen, nil
+}
+
+// Abort closes the stream and deletes all file chunks that have already been written.
+func (us *GridFSUploadStream) Abort() error {
+ defer func() {
+ if us.cancel != nil {
+ us.cancel()
+ }
+ }()
+
+ if us.closed {
+ return ErrStreamClosed
+ }
+
+ _, err := us.chunksColl.DeleteMany(us.ctx, bson.D{{"files_id", us.FileID}})
+ if err != nil {
+ return err
+ }
+
+ us.closed = true
+ return nil
+}
+
+// uploadChunks uploads the current buffer as a series of chunks to the bucket
+// if uploadPartial is true, any data at the end of the buffer that is smaller than a chunk will be uploaded as a partial
+// chunk. if it is false, the data will be moved to the front of the buffer.
+// uploadChunks sets us.bufferIndex to the next available index in the buffer after uploading
+func (us *GridFSUploadStream) uploadChunks(ctx context.Context, uploadPartial bool) error {
+ chunks := float64(us.bufferIndex) / float64(us.chunkSize)
+ numChunks := int(math.Ceil(chunks))
+ if !uploadPartial {
+ numChunks = int(math.Floor(chunks))
+ }
+
+ docs := make([]any, numChunks)
+
+ begChunkIndex := us.chunkIndex
+ for i := 0; i < us.bufferIndex; i += int(us.chunkSize) {
+ endIndex := i + int(us.chunkSize)
+ if us.bufferIndex-i < int(us.chunkSize) {
+ // partial chunk
+ if !uploadPartial {
+ break
+ }
+ endIndex = us.bufferIndex
+ }
+ chunkData := us.buffer[i:endIndex]
+ docs[us.chunkIndex-begChunkIndex] = bson.D{
+ {"_id", bson.NewObjectID()},
+ {"files_id", us.FileID},
+ {"n", int32(us.chunkIndex)},
+ {"data", bson.Binary{Subtype: 0x00, Data: chunkData}},
+ }
+ us.chunkIndex++
+ us.fileLen += int64(len(chunkData))
+ }
+
+ _, err := us.chunksColl.InsertMany(ctx, docs)
+ if err != nil {
+ return err
+ }
+
+ // copy any remaining bytes to beginning of buffer and set buffer index
+ bytesUploaded := numChunks * int(us.chunkSize)
+ if bytesUploaded != uploadBufferSize && !uploadPartial {
+ copy(us.buffer[0:], us.buffer[bytesUploaded:us.bufferIndex])
+ }
+ us.bufferIndex = uploadBufferSize - bytesUploaded
+ return nil
+}
+
+func (us *GridFSUploadStream) createFilesCollDoc(ctx context.Context) error {
+ doc := bson.D{
+ {"_id", us.FileID},
+ {"length", us.fileLen},
+ {"chunkSize", us.chunkSize},
+ {"uploadDate", bson.DateTime(time.Now().UnixNano() / int64(time.Millisecond))},
+ {"filename", us.filename},
+ }
+
+ if us.metadata != nil {
+ doc = append(doc, bson.E{"metadata", us.metadata})
+ }
+
+ _, err := us.filesColl.InsertOne(ctx, doc)
+ if err != nil {
+ return err
+ }
+
+ return nil
+}
diff --git a/vendor/go.mongodb.org/mongo-driver/v2/mongo/index_view.go b/vendor/go.mongodb.org/mongo-driver/v2/mongo/index_view.go
new file mode 100644
index 0000000..9216358
--- /dev/null
+++ b/vendor/go.mongodb.org/mongo-driver/v2/mongo/index_view.go
@@ -0,0 +1,555 @@
+// Copyright (C) MongoDB, Inc. 2017-present.
+//
+// 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
+
+package mongo
+
+import (
+ "bytes"
+ "context"
+ "errors"
+ "fmt"
+ "strconv"
+
+ "go.mongodb.org/mongo-driver/v2/internal/mongoutil"
+ "go.mongodb.org/mongo-driver/v2/internal/optionsutil"
+ "go.mongodb.org/mongo-driver/v2/internal/serverselector"
+ "go.mongodb.org/mongo-driver/v2/mongo/options"
+ "go.mongodb.org/mongo-driver/v2/mongo/readpref"
+ "go.mongodb.org/mongo-driver/v2/x/bsonx/bsoncore"
+ "go.mongodb.org/mongo-driver/v2/x/mongo/driver"
+ "go.mongodb.org/mongo-driver/v2/x/mongo/driver/description"
+ "go.mongodb.org/mongo-driver/v2/x/mongo/driver/operation"
+ "go.mongodb.org/mongo-driver/v2/x/mongo/driver/session"
+)
+
+// ErrInvalidIndexValue is returned if an index is created with a keys document that has a value that is not a number
+// or string.
+var ErrInvalidIndexValue = errors.New("invalid index value")
+
+// ErrNonStringIndexName is returned if an index is created with a name that is not a string.
+//
+// Deprecated: it will be removed in the next major release
+var ErrNonStringIndexName = errors.New("index name must be a string")
+
+// IndexView is a type that can be used to create, drop, and list indexes on a collection. An IndexView for a collection
+// can be created by a call to Collection.Indexes().
+type IndexView struct {
+ coll *Collection
+}
+
+// IndexModel represents a new index to be created.
+type IndexModel struct {
+ // A document describing which keys should be used for the index. It cannot be nil. This must be an order-preserving
+ // type such as bson.D. Map types such as bson.M are not valid. See https://www.mongodb.com/docs/manual/indexes/#indexes
+ // for examples of valid documents.
+ Keys any
+
+ // The options to use to create the index.
+ Options *options.IndexOptionsBuilder
+}
+
+// List executes a listIndexes command and returns a cursor over the indexes in the collection.
+//
+// The opts parameter can be used to specify options for this operation (see the options.ListIndexesOptions
+// documentation).
+//
+// For more information about the command, see https://www.mongodb.com/docs/manual/reference/command/listIndexes/.
+func (iv IndexView) List(ctx context.Context, opts ...options.Lister[options.ListIndexesOptions]) (*Cursor, error) {
+ if ctx == nil {
+ ctx = context.Background()
+ }
+
+ sess := sessionFromContext(ctx)
+ if sess == nil && iv.coll.client.sessionPool != nil {
+ sess = session.NewImplicitClientSession(iv.coll.client.sessionPool, iv.coll.client.id)
+ }
+
+ err := iv.coll.client.validSession(sess)
+ if err != nil {
+ closeImplicitSession(sess)
+ return nil, err
+ }
+ var selector description.ServerSelector
+
+ retry := driver.RetryNone
+ if iv.coll.client.retryReads {
+ retry = driver.RetryOncePerCommand
+ }
+
+ cursorOpts := iv.coll.client.createBaseCursorOptions(iv.coll.client.retryReads)
+
+ selector = &serverselector.Composite{
+ Selectors: []description.ServerSelector{
+ &serverselector.ReadPref{ReadPref: readpref.Primary()},
+ &serverselector.Latency{Latency: iv.coll.client.localThreshold},
+ },
+ }
+
+ selector = makeReadPrefSelector(sess, selector, iv.coll.client.localThreshold)
+ op := operation.NewListIndexes().
+ Session(sess).CommandMonitor(iv.coll.client.monitor).
+ ServerSelector(selector).ClusterClock(iv.coll.client.clock).
+ Retry(retry).MaxAdaptiveRetries(cursorOpts.MaxAdaptiveRetries).
+ EnableOverloadRetargeting(cursorOpts.EnableOverloadRetargeting).
+ Database(iv.coll.db.name).Collection(iv.coll.name).
+ Deployment(iv.coll.client.deployment).ServerAPI(iv.coll.client.serverAPI).
+ Timeout(iv.coll.client.timeout).Crypt(iv.coll.client.cryptFLE).Authenticator(iv.coll.client.authenticator)
+
+ cursorOpts.MarshalValueEncoderFn = newEncoderFn(iv.coll.bsonOpts, iv.coll.registry)
+
+ args, err := mongoutil.NewOptions[options.ListIndexesOptions](opts...)
+ if err != nil {
+ return nil, fmt.Errorf("failed to construct options from builder: %w", err)
+ }
+
+ if args.BatchSize != nil {
+ op = op.BatchSize(*args.BatchSize)
+ cursorOpts.BatchSize = *args.BatchSize
+ }
+ if rawData, ok := optionsutil.Value(args.Internal, "rawData").(bool); ok {
+ op = op.RawData(rawData)
+ }
+
+ err = op.Execute(ctx)
+ if err != nil {
+ // for namespaceNotFound errors, return an empty cursor and do not throw an error
+ closeImplicitSession(sess)
+ var de driver.Error
+ if errors.As(err, &de) && de.NamespaceNotFound() {
+ return newEmptyCursor(), nil
+ }
+
+ return nil, wrapErrors(err)
+ }
+
+ bc, err := op.Result(cursorOpts)
+ if err != nil {
+ closeImplicitSession(sess)
+ return nil, wrapErrors(err)
+ }
+ cursor, err := newCursorWithSession(bc, iv.coll.bsonOpts, iv.coll.registry, sess,
+
+ // This value is included for completeness, but a server will never return
+ // a tailable awaitData cursor from a listIndexes operation.
+ withCursorOptionClientTimeout(iv.coll.client.timeout))
+
+ return cursor, wrapErrors(err)
+}
+
+// ListSpecifications executes a List command and returns a slice of returned IndexSpecifications
+func (iv IndexView) ListSpecifications(
+ ctx context.Context,
+ opts ...options.Lister[options.ListIndexesOptions],
+) ([]IndexSpecification, error) {
+ cursor, err := iv.List(ctx, opts...)
+ if err != nil {
+ return nil, err
+ }
+
+ var resp []indexListSpecificationResponse
+
+ if err := cursor.All(ctx, &resp); err != nil {
+ return nil, err
+ }
+
+ namespace := iv.coll.db.Name() + "." + iv.coll.Name()
+
+ specs := make([]IndexSpecification, len(resp))
+ for idx, spec := range resp {
+ specs[idx] = IndexSpecification(spec)
+ specs[idx].Namespace = namespace
+ }
+
+ return specs, nil
+}
+
+// CreateOne executes a createIndexes command to create an index on the collection and returns the name of the new
+// index. See the IndexView.CreateMany documentation for more information and an example.
+func (iv IndexView) CreateOne(
+ ctx context.Context,
+ model IndexModel,
+ opts ...options.Lister[options.CreateIndexesOptions],
+) (string, error) {
+ names, err := iv.CreateMany(ctx, []IndexModel{model}, opts...)
+ if err != nil {
+ return "", err
+ }
+
+ return names[0], nil
+}
+
+// CreateMany executes a createIndexes command to create multiple indexes on the collection and returns the names of
+// the new indexes.
+//
+// For each IndexModel in the models parameter, the index name can be specified via the Options field. If a name is not
+// given, it will be generated from the Keys document.
+//
+// The opts parameter can be used to specify options for this operation (see the options.CreateIndexesOptions
+// documentation).
+//
+// For more information about the command, see https://www.mongodb.com/docs/manual/reference/command/createIndexes/.
+func (iv IndexView) CreateMany(
+ ctx context.Context,
+ models []IndexModel,
+ opts ...options.Lister[options.CreateIndexesOptions],
+) ([]string, error) {
+ names := make([]string, 0, len(models))
+
+ var indexes bsoncore.Document
+ aidx, indexes := bsoncore.AppendArrayStart(indexes)
+
+ for i, model := range models {
+ if model.Keys == nil {
+ return nil, fmt.Errorf("index model keys cannot be nil")
+ }
+
+ if isUnorderedMap(model.Keys) {
+ return nil, ErrMapForOrderedArgument{"keys"}
+ }
+
+ keys, err := marshal(model.Keys, iv.coll.bsonOpts, iv.coll.registry)
+ if err != nil {
+ return nil, err
+ }
+
+ name, err := getOrGenerateIndexName(keys, model)
+ if err != nil {
+ return nil, err
+ }
+
+ names = append(names, name)
+
+ var iidx int32
+ iidx, indexes = bsoncore.AppendDocumentElementStart(indexes, strconv.Itoa(i))
+ indexes = bsoncore.AppendDocumentElement(indexes, "key", keys)
+
+ if model.Options == nil {
+ model.Options = options.Index()
+ }
+ model.Options.SetName(name)
+
+ optsDoc, err := iv.createOptionsDoc(model.Options)
+ if err != nil {
+ return nil, err
+ }
+
+ indexes = bsoncore.AppendDocument(indexes, optsDoc)
+
+ indexes, err = bsoncore.AppendDocumentEnd(indexes, iidx)
+ if err != nil {
+ return nil, err
+ }
+ }
+
+ indexes, err := bsoncore.AppendArrayEnd(indexes, aidx)
+ if err != nil {
+ return nil, err
+ }
+
+ sess := sessionFromContext(ctx)
+
+ if sess == nil && iv.coll.client.sessionPool != nil {
+ sess = session.NewImplicitClientSession(iv.coll.client.sessionPool, iv.coll.client.id)
+ defer sess.EndSession()
+ }
+
+ err = iv.coll.client.validSession(sess)
+ if err != nil {
+ return nil, err
+ }
+
+ wc := iv.coll.writeConcern
+ if sess.TransactionRunning() {
+ wc = nil
+ }
+ if !wc.Acknowledged() {
+ sess = nil
+ }
+
+ maxAdaptiveRetries := iv.coll.client.effectiveAdaptiveRetries(iv.coll.client.retryWrites)
+
+ selector := makePinnedSelector(sess, iv.coll.writeSelector)
+
+ args, err := mongoutil.NewOptions[options.CreateIndexesOptions](opts...)
+ if err != nil {
+ return nil, fmt.Errorf("failed to construct options from builder: %w", err)
+ }
+
+ op := operation.NewCreateIndexes(indexes).
+ Session(sess).WriteConcern(wc).ClusterClock(iv.coll.client.clock).
+ MaxAdaptiveRetries(maxAdaptiveRetries).EnableOverloadRetargeting(iv.coll.client.enableOverloadRetargeting).
+ Database(iv.coll.db.name).Collection(iv.coll.name).CommandMonitor(iv.coll.client.monitor).
+ Deployment(iv.coll.client.deployment).ServerSelector(selector).ServerAPI(iv.coll.client.serverAPI).
+ Timeout(iv.coll.client.timeout).Crypt(iv.coll.client.cryptFLE).Authenticator(iv.coll.client.authenticator)
+ if args.CommitQuorum != nil {
+ commitQuorum, err := marshalValue(args.CommitQuorum, iv.coll.bsonOpts, iv.coll.registry)
+ if err != nil {
+ return nil, err
+ }
+
+ op.CommitQuorum(commitQuorum)
+ }
+ if rawData, ok := optionsutil.Value(args.Internal, "rawData").(bool); ok {
+ op = op.RawData(rawData)
+ }
+
+ _, err = processWriteError(op.Execute(ctx))
+ if err != nil {
+ return nil, err
+ }
+
+ return names, nil
+}
+
+func (iv IndexView) createOptionsDoc(opts options.Lister[options.IndexOptions]) (bsoncore.Document, error) {
+ args, err := mongoutil.NewOptions[options.IndexOptions](opts)
+ if err != nil {
+ return nil, fmt.Errorf("failed to construct options from builder: %w", err)
+ }
+
+ optsDoc := bsoncore.Document{}
+ if args.ExpireAfterSeconds != nil {
+ optsDoc = bsoncore.AppendInt32Element(optsDoc, "expireAfterSeconds", *args.ExpireAfterSeconds)
+ }
+ if args.Name != nil {
+ optsDoc = bsoncore.AppendStringElement(optsDoc, "name", *args.Name)
+ }
+ if args.Sparse != nil {
+ optsDoc = bsoncore.AppendBooleanElement(optsDoc, "sparse", *args.Sparse)
+ }
+ if args.StorageEngine != nil {
+ doc, err := marshal(args.StorageEngine, iv.coll.bsonOpts, iv.coll.registry)
+ if err != nil {
+ return nil, err
+ }
+
+ optsDoc = bsoncore.AppendDocumentElement(optsDoc, "storageEngine", doc)
+ }
+ if args.Unique != nil {
+ optsDoc = bsoncore.AppendBooleanElement(optsDoc, "unique", *args.Unique)
+ }
+ if args.Version != nil {
+ optsDoc = bsoncore.AppendInt32Element(optsDoc, "v", *args.Version)
+ }
+ if args.DefaultLanguage != nil {
+ optsDoc = bsoncore.AppendStringElement(optsDoc, "default_language", *args.DefaultLanguage)
+ }
+ if args.LanguageOverride != nil {
+ optsDoc = bsoncore.AppendStringElement(optsDoc, "language_override", *args.LanguageOverride)
+ }
+ if args.TextVersion != nil {
+ optsDoc = bsoncore.AppendInt32Element(optsDoc, "textIndexVersion", *args.TextVersion)
+ }
+ if args.Weights != nil {
+ doc, err := marshal(args.Weights, iv.coll.bsonOpts, iv.coll.registry)
+ if err != nil {
+ return nil, err
+ }
+
+ optsDoc = bsoncore.AppendDocumentElement(optsDoc, "weights", doc)
+ }
+ if args.SphereVersion != nil {
+ optsDoc = bsoncore.AppendInt32Element(optsDoc, "2dsphereIndexVersion", *args.SphereVersion)
+ }
+ if args.Bits != nil {
+ optsDoc = bsoncore.AppendInt32Element(optsDoc, "bits", *args.Bits)
+ }
+ if args.Max != nil {
+ optsDoc = bsoncore.AppendDoubleElement(optsDoc, "max", *args.Max)
+ }
+ if args.Min != nil {
+ optsDoc = bsoncore.AppendDoubleElement(optsDoc, "min", *args.Min)
+ }
+ if args.BucketSize != nil {
+ optsDoc = bsoncore.AppendInt32Element(optsDoc, "bucketSize", *args.BucketSize)
+ }
+ if args.PartialFilterExpression != nil {
+ doc, err := marshal(args.PartialFilterExpression, iv.coll.bsonOpts, iv.coll.registry)
+ if err != nil {
+ return nil, err
+ }
+
+ optsDoc = bsoncore.AppendDocumentElement(optsDoc, "partialFilterExpression", doc)
+ }
+ if args.Collation != nil {
+ optsDoc = bsoncore.AppendDocumentElement(optsDoc, "collation", bsoncore.Document(toDocument(args.Collation)))
+ }
+ if args.WildcardProjection != nil {
+ doc, err := marshal(args.WildcardProjection, iv.coll.bsonOpts, iv.coll.registry)
+ if err != nil {
+ return nil, err
+ }
+
+ optsDoc = bsoncore.AppendDocumentElement(optsDoc, "wildcardProjection", doc)
+ }
+ if args.Hidden != nil {
+ optsDoc = bsoncore.AppendBooleanElement(optsDoc, "hidden", *args.Hidden)
+ }
+
+ return optsDoc, nil
+}
+
+func (iv IndexView) drop(ctx context.Context, index any, opts ...options.Lister[options.DropIndexesOptions]) error {
+ args, err := mongoutil.NewOptions[options.DropIndexesOptions](opts...)
+ if err != nil {
+ return fmt.Errorf("failed to construct options from builder: %w", err)
+ }
+
+ if ctx == nil {
+ ctx = context.Background()
+ }
+
+ sess := sessionFromContext(ctx)
+ if sess == nil && iv.coll.client.sessionPool != nil {
+ sess = session.NewImplicitClientSession(iv.coll.client.sessionPool, iv.coll.client.id)
+ defer sess.EndSession()
+ }
+
+ err = iv.coll.client.validSession(sess)
+ if err != nil {
+ return err
+ }
+
+ wc := iv.coll.writeConcern
+ if sess.TransactionRunning() {
+ wc = nil
+ }
+ if !wc.Acknowledged() {
+ sess = nil
+ }
+
+ maxAdaptiveRetries := iv.coll.client.effectiveAdaptiveRetries(iv.coll.client.retryWrites)
+
+ selector := makePinnedSelector(sess, iv.coll.writeSelector)
+
+ op := operation.NewDropIndexes(index).Session(sess).WriteConcern(wc).CommandMonitor(iv.coll.client.monitor).
+ MaxAdaptiveRetries(maxAdaptiveRetries).EnableOverloadRetargeting(iv.coll.client.enableOverloadRetargeting).
+ ServerSelector(selector).ClusterClock(iv.coll.client.clock).
+ Database(iv.coll.db.name).Collection(iv.coll.name).
+ Deployment(iv.coll.client.deployment).ServerAPI(iv.coll.client.serverAPI).
+ Timeout(iv.coll.client.timeout).Crypt(iv.coll.client.cryptFLE).Authenticator(iv.coll.client.authenticator)
+
+ if rawData, ok := optionsutil.Value(args.Internal, "rawData").(bool); ok {
+ op = op.RawData(rawData)
+ }
+
+ err = op.Execute(ctx)
+ if err != nil {
+ return wrapErrors(err)
+ }
+
+ return nil
+}
+
+// DropOne executes a dropIndexes operation to drop an index on the collection.
+//
+// The name parameter should be the name of the index to drop. If the name is
+// "*", ErrMultipleIndexDrop will be returned without running the command
+// because doing so would drop all indexes.
+//
+// The opts parameter can be used to specify options for this operation (see the
+// options.DropIndexesOptions documentation).
+//
+// For more information about the command, see https://www.mongodb.com/docs/manual/reference/command/dropIndexes/.
+func (iv IndexView) DropOne(
+ ctx context.Context,
+ name string,
+ opts ...options.Lister[options.DropIndexesOptions],
+) error {
+ // For more information about the command, see
+ // https://www.mongodb.com/docs/manual/reference/command/dropIndexes/.
+ if name == "*" {
+ return ErrMultipleIndexDrop
+ }
+
+ return iv.drop(ctx, name, opts...)
+}
+
+// DropWithKey drops a collection index by key using the dropIndexes operation.
+//
+// This function is useful to drop an index using its key specification instead of its name.
+func (iv IndexView) DropWithKey(ctx context.Context, keySpecDocument any, opts ...options.Lister[options.DropIndexesOptions]) error {
+ doc, err := marshal(keySpecDocument, iv.coll.bsonOpts, iv.coll.registry)
+ if err != nil {
+ return err
+ }
+
+ return iv.drop(ctx, doc, opts...)
+}
+
+// DropAll executes a dropIndexes operation to drop all indexes on the collection.
+//
+// The opts parameter can be used to specify options for this operation (see the
+// options.DropIndexesOptions documentation).
+//
+// For more information about the command, see
+// https://www.mongodb.com/docs/manual/reference/command/dropIndexes/.
+func (iv IndexView) DropAll(
+ ctx context.Context,
+ opts ...options.Lister[options.DropIndexesOptions],
+) error {
+ return iv.drop(ctx, "*", opts...)
+}
+
+func getOrGenerateIndexName(keySpecDocument bsoncore.Document, model IndexModel) (string, error) {
+ args, err := mongoutil.NewOptions[options.IndexOptions](model.Options)
+ if err != nil {
+ return "", fmt.Errorf("failed to construct options from builder: %w", err)
+ }
+
+ if args != nil && args.Name != nil {
+ return *args.Name, nil
+ }
+
+ name := bytes.NewBufferString("")
+ first := true
+
+ elems, err := keySpecDocument.Elements()
+ if err != nil {
+ return "", err
+ }
+ for _, elem := range elems {
+ if !first {
+ _, err := name.WriteRune('_')
+ if err != nil {
+ return "", err
+ }
+ }
+
+ _, err := name.WriteString(elem.Key())
+ if err != nil {
+ return "", err
+ }
+
+ _, err = name.WriteRune('_')
+ if err != nil {
+ return "", err
+ }
+
+ var value string
+
+ bsonValue := elem.Value()
+ switch bsonValue.Type {
+ case bsoncore.TypeInt32:
+ value = fmt.Sprintf("%d", bsonValue.Int32())
+ case bsoncore.TypeInt64:
+ value = fmt.Sprintf("%d", bsonValue.Int64())
+ case bsoncore.TypeString:
+ value = bsonValue.StringValue()
+ default:
+ return "", ErrInvalidIndexValue
+ }
+
+ _, err = name.WriteString(value)
+ if err != nil {
+ return "", err
+ }
+
+ first = false
+ }
+
+ return name.String(), nil
+}
diff --git a/vendor/go.mongodb.org/mongo-driver/v2/mongo/insert.go b/vendor/go.mongodb.org/mongo-driver/v2/mongo/insert.go
new file mode 100644
index 0000000..ba05b16
--- /dev/null
+++ b/vendor/go.mongodb.org/mongo-driver/v2/mongo/insert.go
@@ -0,0 +1,147 @@
+// Copyright (C) MongoDB, Inc. 2019-present.
+//
+// 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
+
+package mongo
+
+import (
+ "context"
+ "errors"
+ "fmt"
+ "time"
+
+ "go.mongodb.org/mongo-driver/v2/bson"
+ "go.mongodb.org/mongo-driver/v2/event"
+ "go.mongodb.org/mongo-driver/v2/internal/driverutil"
+ "go.mongodb.org/mongo-driver/v2/internal/logger"
+ "go.mongodb.org/mongo-driver/v2/mongo/writeconcern"
+ "go.mongodb.org/mongo-driver/v2/x/bsonx/bsoncore"
+ "go.mongodb.org/mongo-driver/v2/x/mongo/driver"
+ "go.mongodb.org/mongo-driver/v2/x/mongo/driver/description"
+ "go.mongodb.org/mongo-driver/v2/x/mongo/driver/session"
+)
+
+// insert performs an insert operation.
+type insert struct {
+ authenticator driver.Authenticator
+ bypassDocumentValidation *bool
+ comment bsoncore.Value
+ documents []bsoncore.Document
+ ordered *bool
+ session *session.Client
+ clock *session.ClusterClock
+ collection string
+ monitor *event.CommandMonitor
+ crypt driver.Crypt
+ database string
+ deployment driver.Deployment
+ selector description.ServerSelector
+ writeConcern *writeconcern.WriteConcern
+ retry *driver.RetryMode
+ maxAdaptiveRetries uint
+ enableOverloadRetargeting bool
+ result insertResult
+ serverAPI *driver.ServerAPIOptions
+ timeout *time.Duration
+ rawData *bool
+ additionalCmd bson.D
+ logger *logger.Logger
+}
+
+// insertResult represents an insert result returned by the server.
+type insertResult struct {
+ // Number of documents successfully inserted.
+ N int64
+}
+
+func buildInsertResult(response bsoncore.Document) (insertResult, error) {
+ elements, err := response.Elements()
+ if err != nil {
+ return insertResult{}, err
+ }
+ ir := insertResult{}
+ for _, element := range elements {
+ if element.Key() == "n" {
+ var ok bool
+ ir.N, ok = element.Value().AsInt64OK()
+ if !ok {
+ return ir, fmt.Errorf("response field 'n' is type int32 or int64, but received BSON type %s", element.Value().Type)
+ }
+ }
+ }
+ return ir, nil
+}
+
+// Result returns the result of executing this operation.
+func (i *insert) Result() insertResult { return i.result }
+
+func (i *insert) processResponse(_ context.Context, resp bsoncore.Document, _ driver.ResponseInfo) error {
+ ir, err := buildInsertResult(resp)
+ i.result.N += ir.N
+ return err
+}
+
+// Execute runs this operations and returns an error if the operation did not execute successfully.
+func (i *insert) Execute(ctx context.Context) error {
+ if i.deployment == nil {
+ return errors.New("the Insert operation must have a Deployment set before Execute can be called")
+ }
+ batches := &driver.Batches{
+ Identifier: "documents",
+ Documents: i.documents,
+ Ordered: i.ordered,
+ }
+
+ return driver.Operation{
+ CommandFn: i.command,
+ ProcessResponseFn: i.processResponse,
+ Batches: batches,
+ RetryMode: i.retry,
+ MaxAdaptiveRetries: i.maxAdaptiveRetries,
+ EnableOverloadRetargeting: i.enableOverloadRetargeting,
+ Type: driver.Write,
+ Client: i.session,
+ Clock: i.clock,
+ CommandMonitor: i.monitor,
+ Crypt: i.crypt,
+ Database: i.database,
+ Deployment: i.deployment,
+ Selector: i.selector,
+ WriteConcern: i.writeConcern,
+ ServerAPI: i.serverAPI,
+ Timeout: i.timeout,
+ Logger: i.logger,
+ Name: driverutil.InsertOp,
+ Authenticator: i.authenticator,
+ SendAfterClusterTime: true,
+ }.Execute(ctx)
+}
+
+func (i *insert) command(dst []byte, desc description.SelectedServer) ([]byte, error) {
+ dst = bsoncore.AppendStringElement(dst, "insert", i.collection)
+ if i.bypassDocumentValidation != nil && (desc.WireVersion != nil &&
+ driverutil.VersionRangeIncludes(*desc.WireVersion, 4)) {
+
+ dst = bsoncore.AppendBooleanElement(dst, "bypassDocumentValidation", *i.bypassDocumentValidation)
+ }
+ if i.comment.Type != bsoncore.Type(0) {
+ dst = bsoncore.AppendValueElement(dst, "comment", i.comment)
+ }
+ if i.ordered != nil {
+ dst = bsoncore.AppendBooleanElement(dst, "ordered", *i.ordered)
+ }
+ // Set rawData for 8.2+ servers.
+ if i.rawData != nil && desc.WireVersion != nil && driverutil.VersionRangeIncludes(*desc.WireVersion, 27) {
+ dst = bsoncore.AppendBooleanElement(dst, "rawData", *i.rawData)
+ }
+ if len(i.additionalCmd) > 0 {
+ doc, err := bson.Marshal(i.additionalCmd)
+ if err != nil {
+ return nil, err
+ }
+ dst = append(dst, doc[4:len(doc)-1]...)
+ }
+ return dst, nil
+}
diff --git a/vendor/go.mongodb.org/mongo-driver/v2/mongo/mongo.go b/vendor/go.mongodb.org/mongo-driver/v2/mongo/mongo.go
new file mode 100644
index 0000000..703115f
--- /dev/null
+++ b/vendor/go.mongodb.org/mongo-driver/v2/mongo/mongo.go
@@ -0,0 +1,436 @@
+// Copyright (C) MongoDB, Inc. 2017-present.
+//
+// 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
+
+package mongo
+
+import (
+ "bytes"
+ "context"
+ "errors"
+ "fmt"
+ "io"
+ "net"
+ "reflect"
+ "strconv"
+ "strings"
+
+ "go.mongodb.org/mongo-driver/v2/internal/codecutil"
+ "go.mongodb.org/mongo-driver/v2/mongo/options"
+ "go.mongodb.org/mongo-driver/v2/x/bsonx/bsoncore"
+
+ "go.mongodb.org/mongo-driver/v2/bson"
+)
+
+var defaultRegistry = bson.NewRegistry()
+
+// Dialer is used to make network connections.
+type Dialer interface {
+ DialContext(ctx context.Context, network, address string) (net.Conn, error)
+}
+
+// Pipeline is a type that makes creating aggregation pipelines easier. It is a
+// helper and is intended for serializing to BSON.
+//
+// Example usage:
+//
+// mongo.Pipeline{
+// {{"$group", bson.D{{"_id", "$state"}, {"totalPop", bson.D{{"$sum", "$pop"}}}}}},
+// {{"$match", bson.D{{"totalPop", bson.D{{"$gte", 10*1000*1000}}}}}},
+// }
+type Pipeline []bson.D
+
+// getEncoder takes a writer, BSON options, and a BSON registry and returns a properly configured
+// bson.Encoder that writes to the given writer.
+func getEncoder(
+ w io.Writer,
+ opts *options.BSONOptions,
+ reg *bson.Registry,
+) *bson.Encoder {
+ vw := bson.NewDocumentWriter(w)
+ enc := bson.NewEncoder(vw)
+
+ if opts != nil {
+ if opts.ErrorOnInlineDuplicates {
+ enc.ErrorOnInlineDuplicates()
+ }
+ if opts.IntMinSize {
+ enc.IntMinSize()
+ }
+ if opts.NilByteSliceAsEmpty {
+ enc.NilByteSliceAsEmpty()
+ }
+ if opts.NilMapAsEmpty {
+ enc.NilMapAsEmpty()
+ }
+ if opts.NilSliceAsEmpty {
+ enc.NilSliceAsEmpty()
+ }
+ if opts.OmitZeroStruct {
+ enc.OmitZeroStruct()
+ }
+ if opts.OmitEmpty {
+ enc.OmitEmpty()
+ }
+ if opts.StringifyMapKeysWithFmt {
+ enc.StringifyMapKeysWithFmt()
+ }
+ if opts.UseJSONStructTags {
+ enc.UseJSONStructTags()
+ }
+ }
+
+ if reg != nil {
+ enc.SetRegistry(reg)
+ }
+
+ return enc
+}
+
+// newEncoderFn will return a function for constructing an encoder based on the
+// provided codec options.
+func newEncoderFn(opts *options.BSONOptions, registry *bson.Registry) codecutil.EncoderFn {
+ return func(w io.Writer) *bson.Encoder {
+ return getEncoder(w, opts, registry)
+ }
+}
+
+// marshal marshals the given value as a BSON document. Byte slices are always converted to a
+// bson.Raw before marshaling.
+//
+// If bsonOpts and registry are specified, the encoder is configured with the requested behaviors.
+// If they are nil, the default behaviors are used.
+func marshal(
+ val any,
+ bsonOpts *options.BSONOptions,
+ registry *bson.Registry,
+) (bsoncore.Document, error) {
+ if registry == nil {
+ registry = defaultRegistry
+ }
+ if val == nil {
+ return nil, ErrNilDocument
+ }
+ if bs, ok := val.([]byte); ok {
+ // Slight optimization so we'll just use MarshalBSON and not go through the codec machinery.
+ val = bson.Raw(bs)
+ }
+
+ buf := new(bytes.Buffer)
+ enc := getEncoder(buf, bsonOpts, registry)
+ err := enc.Encode(val)
+ if err != nil {
+ return nil, MarshalError{Value: val, Err: err}
+ }
+
+ return buf.Bytes(), nil
+}
+
+// ensureID inserts the given ObjectID as an element named "_id" at the
+// beginning of the given BSON document if there is not an "_id" already.
+// If the given ObjectID is bson.NilObjectID, a new object ID will be
+// generated with time.Now().
+//
+// If there is already an element named "_id", the document is not modified. It
+// returns the resulting document and the decoded Go value of the "_id" element.
+func ensureID(
+ doc bsoncore.Document,
+ oid bson.ObjectID,
+ bsonOpts *options.BSONOptions,
+ reg *bson.Registry,
+) (bsoncore.Document, any, error) {
+ if reg == nil {
+ reg = defaultRegistry
+ }
+
+ // Try to find the "_id" element. If it exists, try to unmarshal just the
+ // "_id" field as an any and return it along with the unmodified
+ // BSON document.
+ if _, err := doc.LookupErr("_id"); err == nil {
+ var id struct {
+ ID any `bson:"_id"`
+ }
+ dec := getDecoder(doc, bsonOpts, reg)
+ err = dec.Decode(&id)
+ if err != nil {
+ return nil, nil, fmt.Errorf("error unmarshaling BSON document: %w", err)
+ }
+
+ return doc, id.ID, nil
+ }
+
+ // We couldn't find an "_id" element, so add one with the value of the
+ // provided ObjectID.
+
+ olddoc := doc
+
+ // Reserve an extra 17 bytes for the "_id" field we're about to add:
+ // type (1) + "_id" (3) + terminator (1) + object ID (12)
+ const extraSpace = 17
+ doc = make(bsoncore.Document, 0, len(olddoc)+extraSpace)
+ _, doc = bsoncore.ReserveLength(doc)
+ if oid.IsZero() {
+ oid = bson.NewObjectID()
+ }
+ doc = bsoncore.AppendObjectIDElement(doc, "_id", oid)
+
+ // Remove and re-write the BSON document length header.
+ const int32Len = 4
+ doc = append(doc, olddoc[int32Len:]...)
+ doc = bsoncore.UpdateLength(doc, 0, int32(len(doc)))
+
+ return doc, oid, nil
+}
+
+func ensureDollarKey(doc bsoncore.Document) error {
+ firstElem, err := doc.IndexErr(0)
+ if err != nil {
+ return errors.New("update document must have at least one element")
+ }
+
+ if !strings.HasPrefix(firstElem.Key(), "$") {
+ return errors.New("update document must contain key beginning with '$'")
+ }
+ return nil
+}
+
+func ensureNoDollarKey(doc bsoncore.Document) error {
+ if elem, err := doc.IndexErr(0); err == nil && strings.HasPrefix(elem.Key(), "$") {
+ return errors.New("replacement document cannot contain keys beginning with '$'")
+ }
+
+ return nil
+}
+
+func marshalAggregatePipeline(
+ pipeline any,
+ bsonOpts *options.BSONOptions,
+ registry *bson.Registry,
+) (bsoncore.Document, bool, error) {
+ switch t := pipeline.(type) {
+ case bson.ValueMarshaler:
+ btype, val, err := t.MarshalBSONValue()
+ if err != nil {
+ return nil, false, err
+ }
+ if typ := bson.Type(btype); typ != bson.TypeArray {
+ return nil, false, fmt.Errorf("ValueMarshaler returned a %v, but was expecting %v", typ, bson.TypeArray)
+ }
+
+ var hasOutputStage bool
+ pipelineDoc := bsoncore.Document(val)
+ values, _ := pipelineDoc.Values()
+ if pipelineLen := len(values); pipelineLen > 0 {
+ if finalDoc, ok := values[pipelineLen-1].DocumentOK(); ok {
+ if elem, err := finalDoc.IndexErr(0); err == nil && (elem.Key() == "$out" || elem.Key() == "$merge") {
+ hasOutputStage = true
+ }
+ }
+ }
+
+ return pipelineDoc, hasOutputStage, nil
+ default:
+ val := reflect.ValueOf(t)
+ if !val.IsValid() || (val.Kind() != reflect.Slice && val.Kind() != reflect.Array) {
+ return nil, false, fmt.Errorf("can only marshal slices and arrays into aggregation pipelines, but got %v", val.Kind())
+ }
+
+ var hasOutputStage bool
+ valLen := val.Len()
+
+ switch t := pipeline.(type) {
+ // Explicitly forbid non-empty pipelines that are semantically single documents
+ // and are implemented as slices.
+ case bson.D, bson.Raw, bsoncore.Document:
+ if valLen > 0 {
+ return nil, false,
+ fmt.Errorf("%T is not an allowed pipeline type as it represents a single document. Use bson.A or mongo.Pipeline instead", t)
+ }
+ // bsoncore.Arrays do not need to be marshaled. Only check validity and presence of output stage.
+ case bsoncore.Array:
+ if err := t.Validate(); err != nil {
+ return nil, false, err
+ }
+
+ values, err := t.Values()
+ if err != nil {
+ return nil, false, err
+ }
+
+ numVals := len(values)
+ if numVals == 0 {
+ return bsoncore.Document(t), false, nil
+ }
+
+ // If not empty, check if first value of the last stage is $out or $merge.
+ if lastStage, ok := values[numVals-1].DocumentOK(); ok {
+ if elem, err := lastStage.IndexErr(0); err == nil && (elem.Key() == "$out" || elem.Key() == "$merge") {
+ hasOutputStage = true
+ }
+ }
+ return bsoncore.Document(t), hasOutputStage, nil
+ }
+
+ aidx, arr := bsoncore.AppendArrayStart(nil)
+ for idx := 0; idx < valLen; idx++ {
+ doc, err := marshal(val.Index(idx).Interface(), bsonOpts, registry)
+ if err != nil {
+ return nil, false, err
+ }
+
+ if idx == valLen-1 {
+ if elem, err := doc.IndexErr(0); err == nil && (elem.Key() == "$out" || elem.Key() == "$merge") {
+ hasOutputStage = true
+ }
+ }
+ arr = bsoncore.AppendDocumentElement(arr, strconv.Itoa(idx), doc)
+ }
+ arr, _ = bsoncore.AppendArrayEnd(arr, aidx)
+ return arr, hasOutputStage, nil
+ }
+}
+
+func marshalUpdateValue(
+ update any,
+ bsonOpts *options.BSONOptions,
+ registry *bson.Registry,
+ dollarKeysAllowed bool,
+) (bsoncore.Value, error) {
+ documentCheckerFunc := ensureDollarKey
+ if !dollarKeysAllowed {
+ documentCheckerFunc = ensureNoDollarKey
+ }
+
+ var u bsoncore.Value
+ var err error
+ switch t := update.(type) {
+ case nil:
+ return u, ErrNilDocument
+ case bson.D:
+ u.Type = bsoncore.TypeEmbeddedDocument
+ u.Data, err = marshal(update, bsonOpts, registry)
+ if err != nil {
+ return u, err
+ }
+
+ return u, documentCheckerFunc(u.Data)
+ case bson.Raw:
+ u.Type = bsoncore.TypeEmbeddedDocument
+ u.Data = t
+ return u, documentCheckerFunc(u.Data)
+ case bsoncore.Document:
+ u.Type = bsoncore.TypeEmbeddedDocument
+ u.Data = t
+ return u, documentCheckerFunc(u.Data)
+ case []byte:
+ u.Type = bsoncore.TypeEmbeddedDocument
+ u.Data = t
+ return u, documentCheckerFunc(u.Data)
+ case bson.Marshaler:
+ u.Type = bsoncore.TypeEmbeddedDocument
+ u.Data, err = t.MarshalBSON()
+ if err != nil {
+ return u, err
+ }
+
+ return u, documentCheckerFunc(u.Data)
+ case bson.ValueMarshaler:
+ tt, data, err := t.MarshalBSONValue()
+ u.Type = bsoncore.Type(tt)
+ u.Data = data
+ if err != nil {
+ return u, err
+ }
+ if u.Type != bsoncore.TypeArray && u.Type != bsoncore.TypeEmbeddedDocument {
+ return u, fmt.Errorf("ValueMarshaler returned a %v, but was expecting %v or %v", u.Type, bsoncore.TypeArray, bsoncore.TypeEmbeddedDocument)
+ }
+ return u, err
+ default:
+ val := reflect.ValueOf(t)
+ if !val.IsValid() {
+ return u, fmt.Errorf("can only marshal slices and arrays into update pipelines, but got %v", val.Kind())
+ }
+ if val.Kind() != reflect.Slice && val.Kind() != reflect.Array {
+ u.Type = bsoncore.TypeEmbeddedDocument
+ u.Data, err = marshal(update, bsonOpts, registry)
+ if err != nil {
+ return u, err
+ }
+
+ return u, documentCheckerFunc(u.Data)
+ }
+
+ u.Type = bsoncore.TypeArray
+ aidx, arr := bsoncore.AppendArrayStart(nil)
+ valLen := val.Len()
+ for idx := 0; idx < valLen; idx++ {
+ doc, err := marshal(val.Index(idx).Interface(), bsonOpts, registry)
+ if err != nil {
+ return u, err
+ }
+
+ if err := documentCheckerFunc(doc); err != nil {
+ return u, err
+ }
+
+ arr = bsoncore.AppendDocumentElement(arr, strconv.Itoa(idx), doc)
+ }
+ u.Data, _ = bsoncore.AppendArrayEnd(arr, aidx)
+ return u, err
+ }
+}
+
+func marshalValue(
+ val any,
+ bsonOpts *options.BSONOptions,
+ registry *bson.Registry,
+) (bsoncore.Value, error) {
+ return codecutil.MarshalValue(val, newEncoderFn(bsonOpts, registry))
+}
+
+// Build the aggregation pipeline for the CountDocument command.
+func countDocumentsAggregatePipeline(
+ filter any,
+ encOpts *options.BSONOptions,
+ registry *bson.Registry,
+ args *options.CountOptions,
+) (bsoncore.Document, error) {
+ filterDoc, err := marshal(filter, encOpts, registry)
+ if err != nil {
+ return nil, err
+ }
+
+ aidx, arr := bsoncore.AppendArrayStart(nil)
+ didx, arr := bsoncore.AppendDocumentElementStart(arr, strconv.Itoa(0))
+ arr = bsoncore.AppendDocumentElement(arr, "$match", filterDoc)
+ arr, _ = bsoncore.AppendDocumentEnd(arr, didx)
+
+ index := 1
+ if args != nil {
+ if args.Skip != nil {
+ didx, arr = bsoncore.AppendDocumentElementStart(arr, strconv.Itoa(index))
+ arr = bsoncore.AppendInt64Element(arr, "$skip", *args.Skip)
+ arr, _ = bsoncore.AppendDocumentEnd(arr, didx)
+ index++
+ }
+ if args.Limit != nil {
+ didx, arr = bsoncore.AppendDocumentElementStart(arr, strconv.Itoa(index))
+ arr = bsoncore.AppendInt64Element(arr, "$limit", *args.Limit)
+ arr, _ = bsoncore.AppendDocumentEnd(arr, didx)
+ index++
+ }
+ }
+
+ didx, arr = bsoncore.AppendDocumentElementStart(arr, strconv.Itoa(index))
+ iidx, arr := bsoncore.AppendDocumentElementStart(arr, "$group")
+ arr = bsoncore.AppendInt32Element(arr, "_id", 1)
+ iiidx, arr := bsoncore.AppendDocumentElementStart(arr, "n")
+ arr = bsoncore.AppendInt32Element(arr, "$sum", 1)
+ arr, _ = bsoncore.AppendDocumentEnd(arr, iiidx)
+ arr, _ = bsoncore.AppendDocumentEnd(arr, iidx)
+ arr, _ = bsoncore.AppendDocumentEnd(arr, didx)
+
+ return bsoncore.AppendArrayEnd(arr, aidx)
+}
diff --git a/vendor/go.mongodb.org/mongo-driver/v2/mongo/mongocryptd.go b/vendor/go.mongodb.org/mongo-driver/v2/mongo/mongocryptd.go
new file mode 100644
index 0000000..4af950d
--- /dev/null
+++ b/vendor/go.mongodb.org/mongo-driver/v2/mongo/mongocryptd.go
@@ -0,0 +1,166 @@
+// Copyright (C) MongoDB, Inc. 2017-present.
+//
+// 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
+
+package mongo
+
+import (
+ "context"
+ "os/exec"
+ "strings"
+ "time"
+
+ "go.mongodb.org/mongo-driver/v2/mongo/options"
+ "go.mongodb.org/mongo-driver/v2/mongo/readconcern"
+ "go.mongodb.org/mongo-driver/v2/mongo/readpref"
+ "go.mongodb.org/mongo-driver/v2/x/bsonx/bsoncore"
+)
+
+const (
+ defaultServerSelectionTimeout = 10 * time.Second
+ defaultURI = "mongodb://localhost:27020"
+ defaultPath = "mongocryptd"
+ serverSelectionTimeoutStr = "server selection error"
+)
+
+var (
+ defaultTimeoutArgs = []string{"--idleShutdownTimeoutSecs=60"}
+ databaseOpts = options.Database().SetReadConcern(&readconcern.ReadConcern{}).SetReadPreference(readpref.Primary())
+)
+
+type mongocryptdClient struct {
+ bypassSpawn bool
+ client *Client
+ path string
+ spawnArgs []string
+}
+
+// newMongocryptdClient creates a client to mongocryptd.
+// newMongocryptdClient is expected to not be called if the crypt shared library is available.
+// The crypt shared library replaces all mongocryptd functionality.
+func newMongocryptdClient(opts *options.AutoEncryptionOptions) (*mongocryptdClient, error) {
+ // create mcryptClient instance and spawn process if necessary
+ var bypassSpawn bool
+ var bypassAutoEncryption bool
+
+ if bypass, ok := opts.ExtraOptions["mongocryptdBypassSpawn"]; ok {
+ bypassSpawn = bypass.(bool)
+ }
+ if opts.BypassAutoEncryption != nil {
+ bypassAutoEncryption = *opts.BypassAutoEncryption
+ }
+
+ bypassQueryAnalysis := opts.BypassQueryAnalysis != nil && *opts.BypassQueryAnalysis
+
+ mc := &mongocryptdClient{
+ // mongocryptd should not be spawned if any of these conditions are true:
+ // - mongocryptdBypassSpawn is passed
+ // - bypassAutoEncryption is true because mongocryptd is not used during decryption
+ // - bypassQueryAnalysis is true because mongocryptd is not used during decryption
+ bypassSpawn: bypassSpawn || bypassAutoEncryption || bypassQueryAnalysis,
+ }
+
+ if !mc.bypassSpawn {
+ mc.path, mc.spawnArgs = createSpawnArgs(opts.ExtraOptions)
+ if err := mc.spawnProcess(); err != nil {
+ return nil, err
+ }
+ }
+
+ // get connection string
+ uri := defaultURI
+ if u, ok := opts.ExtraOptions["mongocryptdURI"]; ok {
+ uri = u.(string)
+ }
+
+ // create client
+ client, err := newClient(options.Client().ApplyURI(uri).SetServerSelectionTimeout(defaultServerSelectionTimeout))
+ if err != nil {
+ return nil, err
+ }
+ mc.client = client
+
+ return mc, nil
+}
+
+// markCommand executes the given command on mongocryptd.
+func (mc *mongocryptdClient) markCommand(ctx context.Context, dbName string, cmd bsoncore.Document) (bsoncore.Document, error) {
+ // Remove the explicit session from the context if one is set.
+ // The explicit session will be from a different client.
+ // If an explicit session is set, it is applied after automatic encryption.
+ ctx = NewSessionContext(ctx, nil)
+ db := mc.client.Database(dbName, databaseOpts)
+
+ res, err := db.RunCommand(ctx, cmd).Raw()
+ // propagate original result
+ if err == nil {
+ return bsoncore.Document(res), nil
+ }
+ // wrap original error
+ if mc.bypassSpawn || !strings.Contains(err.Error(), serverSelectionTimeoutStr) {
+ return nil, MongocryptdError{Wrapped: err}
+ }
+
+ // re-spawn and retry
+ if err = mc.spawnProcess(); err != nil {
+ return nil, err
+ }
+ res, err = db.RunCommand(ctx, cmd).Raw()
+ if err != nil {
+ return nil, MongocryptdError{Wrapped: err}
+ }
+ return bsoncore.Document(res), nil
+}
+
+// connect connects the underlying Client instance. This must be called before performing any mark operations.
+func (mc *mongocryptdClient) connect() error {
+ return mc.client.connect()
+}
+
+// disconnect disconnects the underlying Client instance. This should be called after all operations have completed.
+func (mc *mongocryptdClient) disconnect(ctx context.Context) error {
+ return mc.client.Disconnect(ctx)
+}
+
+func (mc *mongocryptdClient) spawnProcess() error {
+ // Ignore gosec warning about subprocess launched with externally-provided path variable.
+ /* #nosec G204 */
+ cmd := exec.Command(mc.path, mc.spawnArgs...)
+ cmd.Stdout = nil
+ cmd.Stderr = nil
+ return cmd.Start()
+}
+
+// createSpawnArgs creates arguments to spawn mcryptClient. It returns the path and a slice of arguments.
+func createSpawnArgs(opts map[string]any) (string, []string) {
+ var spawnArgs []string
+
+ // get command path
+ path := defaultPath
+ if p, ok := opts["mongocryptdPath"]; ok {
+ path = p.(string)
+ }
+
+ // add specified options
+ if sa, ok := opts["mongocryptdSpawnArgs"]; ok {
+ spawnArgs = append(spawnArgs, sa.([]string)...)
+ }
+
+ // add timeout options if necessary
+ var foundTimeout bool
+ for _, arg := range spawnArgs {
+ // need to use HasPrefix instead of doing an exact equality check because both
+ // mongocryptd supports both [--idleShutdownTimeoutSecs, 0] and [--idleShutdownTimeoutSecs=0]
+ if strings.HasPrefix(arg, "--idleShutdownTimeoutSecs") {
+ foundTimeout = true
+ break
+ }
+ }
+ if !foundTimeout {
+ spawnArgs = append(spawnArgs, defaultTimeoutArgs...)
+ }
+
+ return path, spawnArgs
+}
diff --git a/vendor/go.mongodb.org/mongo-driver/v2/mongo/mongointernal.go b/vendor/go.mongodb.org/mongo-driver/v2/mongo/mongointernal.go
new file mode 100644
index 0000000..31195d3
--- /dev/null
+++ b/vendor/go.mongodb.org/mongo-driver/v2/mongo/mongointernal.go
@@ -0,0 +1,41 @@
+// Copyright (C) MongoDB, Inc. 2025-present.
+//
+// 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
+
+//go:build mongointernal
+
+package mongo
+
+import (
+ "time"
+
+ "go.mongodb.org/mongo-driver/v2/bson"
+ "go.mongodb.org/mongo-driver/v2/x/bsonx/bsoncore"
+ "go.mongodb.org/mongo-driver/v2/x/mongo/driver/session"
+)
+
+// NewSessionWithLSID returns a Session with the given sessionID document. The
+// sessionID is a BSON document with key "id" containing a 16-byte UUID (binary
+// subtype 4).
+//
+// Sessions returned by NewSessionWithLSID are never added to the driver's
+// session pool. Calling "EndSession" or "ClientSession.SetServer" on a Session
+// returned by NewSessionWithLSID will panic.
+//
+// NewSessionWithLSID is intended only for internal use and may be changed or
+// removed at any time.
+func NewSessionWithLSID(client *Client, sessionID bson.Raw) *Session {
+ return &Session{
+ clientSession: &session.Client{
+ Server: &session.Server{
+ SessionID: bsoncore.Document(sessionID),
+ LastUsed: time.Now(),
+ },
+ ClientID: client.id,
+ },
+ client: client,
+ deployment: client.deployment,
+ }
+}
diff --git a/vendor/go.mongodb.org/mongo-driver/v2/mongo/options/aggregateoptions.go b/vendor/go.mongodb.org/mongo-driver/v2/mongo/options/aggregateoptions.go
new file mode 100644
index 0000000..ad550fd
--- /dev/null
+++ b/vendor/go.mongodb.org/mongo-driver/v2/mongo/options/aggregateoptions.go
@@ -0,0 +1,175 @@
+// Copyright (C) MongoDB, Inc. 2017-present.
+//
+// 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
+
+package options
+
+import (
+ "time"
+
+ "go.mongodb.org/mongo-driver/v2/bson"
+ "go.mongodb.org/mongo-driver/v2/internal/optionsutil"
+)
+
+// AggregateOptions represents arguments that can be used to configure an
+// Aggregate operation.
+//
+// See corresponding setter methods for documentation.
+type AggregateOptions struct {
+ AllowDiskUse *bool
+ BatchSize *int32
+ BypassDocumentValidation *bool
+ Collation *Collation
+ MaxAwaitTime *time.Duration
+ Comment any
+ Hint any
+ Let any
+
+ // Deprecated: Custom is for internal use only and should not be set. It may be changed or removed in any
+ // release.
+ Custom bson.M
+
+ // Deprecated: This option is for internal use only and should not be set. It may be changed or removed in any
+ // release.
+ Internal optionsutil.Options
+}
+
+// AggregateOptionsBuilder contains options to configure aggregate operations.
+// Each option can be set through setter functions. See documentation for each
+// setter function for an explanation of the option.
+type AggregateOptionsBuilder struct {
+ Opts []func(*AggregateOptions) error
+}
+
+// Aggregate creates a new AggregateOptions instance.
+func Aggregate() *AggregateOptionsBuilder {
+ return &AggregateOptionsBuilder{}
+}
+
+// List returns a list of AggergateOptions setter functions.
+func (ao *AggregateOptionsBuilder) List() []func(*AggregateOptions) error {
+ return ao.Opts
+}
+
+// SetAllowDiskUse sets the value for the AllowDiskUse field. If true, the operation can write to temporary
+// files in the _tmp subdirectory of the database directory path on the server. The default value is false.
+func (ao *AggregateOptionsBuilder) SetAllowDiskUse(b bool) *AggregateOptionsBuilder {
+ ao.Opts = append(ao.Opts, func(opts *AggregateOptions) error {
+ opts.AllowDiskUse = &b
+
+ return nil
+ })
+
+ return ao
+}
+
+// SetBatchSize sets the value for the BatchSize field. Specifies the maximum number of documents
+// to be included in each batch returned by the server.
+func (ao *AggregateOptionsBuilder) SetBatchSize(i int32) *AggregateOptionsBuilder {
+ ao.Opts = append(ao.Opts, func(opts *AggregateOptions) error {
+ opts.BatchSize = &i
+
+ return nil
+ })
+
+ return ao
+}
+
+// SetBypassDocumentValidation sets the value for the BypassDocumentValidation field. If true, writes
+// executed as part of the operation will opt out of document-level validation on the server. The default value
+// is false. See https://www.mongodb.com/docs/manual/core/schema-validation/ for more information about
+// document validation.
+func (ao *AggregateOptionsBuilder) SetBypassDocumentValidation(b bool) *AggregateOptionsBuilder {
+ ao.Opts = append(ao.Opts, func(opts *AggregateOptions) error {
+ opts.BypassDocumentValidation = &b
+
+ return nil
+ })
+
+ return ao
+}
+
+// SetCollation sets the value for the Collation field. Specifies a collation to
+// use for string comparisons during the operation. The default value is nil,
+// which means the default collation of the collection will be used.
+func (ao *AggregateOptionsBuilder) SetCollation(c *Collation) *AggregateOptionsBuilder {
+ ao.Opts = append(ao.Opts, func(opts *AggregateOptions) error {
+ opts.Collation = c
+
+ return nil
+ })
+
+ return ao
+}
+
+// SetMaxAwaitTime sets the value for the MaxAwaitTime field. Specifies maximum amount of time
+// that the server should wait for new documents to satisfy a tailable cursor query.
+func (ao *AggregateOptionsBuilder) SetMaxAwaitTime(d time.Duration) *AggregateOptionsBuilder {
+ ao.Opts = append(ao.Opts, func(opts *AggregateOptions) error {
+ opts.MaxAwaitTime = &d
+
+ return nil
+ })
+
+ return ao
+}
+
+// SetComment sets the value for the Comment field. Specifies a string or document that will be included in
+// server logs, profiling logs, and currentOp queries to help trace the operation. The default is nil,
+// which means that no comment will be included in the logs.
+func (ao *AggregateOptionsBuilder) SetComment(comment any) *AggregateOptionsBuilder {
+ ao.Opts = append(ao.Opts, func(opts *AggregateOptions) error {
+ opts.Comment = comment
+
+ return nil
+ })
+
+ return ao
+}
+
+// SetHint sets the value for the Hint field. Specifies the index to use for the aggregation. This should
+// either be the index name as a string or the index specification as a document. The hint does not apply to
+// $lookup and $graphLookup aggregation stages. The driver will return an error if the hint parameter
+// is a multi-key map. The default value is nil, which means that no hint will be sent.
+func (ao *AggregateOptionsBuilder) SetHint(h any) *AggregateOptionsBuilder {
+ ao.Opts = append(ao.Opts, func(opts *AggregateOptions) error {
+ opts.Hint = h
+
+ return nil
+ })
+
+ return ao
+}
+
+// SetLet sets the value for the Let field. Specifies parameters for the aggregate expression. This
+// option is only valid for MongoDB versions >= 5.0. Older servers will report an error for using this
+// option. This must be a document mapping parameter names to values. 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").
+func (ao *AggregateOptionsBuilder) SetLet(let any) *AggregateOptionsBuilder {
+ ao.Opts = append(ao.Opts, func(opts *AggregateOptions) error {
+ opts.Let = let
+
+ return nil
+ })
+
+ return ao
+}
+
+// SetCustom sets the value for the Custom field. Key-value pairs of the BSON map should correlate
+// with desired option names and values. Values must be Marshalable. Custom options may conflict
+// with non-custom options, and custom options bypass client-side validation. Prefer using non-custom
+// options where possible.
+//
+// Deprecated: SetCustom is for internal use only. It may be changed or removed in any release.
+func (ao *AggregateOptionsBuilder) SetCustom(c bson.M) *AggregateOptionsBuilder {
+ ao.Opts = append(ao.Opts, func(opts *AggregateOptions) error {
+ opts.Custom = c
+
+ return nil
+ })
+
+ return ao
+}
diff --git a/vendor/go.mongodb.org/mongo-driver/v2/mongo/options/autoencryptionoptions.go b/vendor/go.mongodb.org/mongo-driver/v2/mongo/options/autoencryptionoptions.go
new file mode 100644
index 0000000..db3508f
--- /dev/null
+++ b/vendor/go.mongodb.org/mongo-driver/v2/mongo/options/autoencryptionoptions.go
@@ -0,0 +1,176 @@
+// Copyright (C) MongoDB, Inc. 2017-present.
+//
+// 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
+
+package options
+
+import (
+ "crypto/tls"
+ "net/http"
+ "time"
+
+ "go.mongodb.org/mongo-driver/v2/internal/httputil"
+)
+
+// AutoEncryptionOptions represents arguments used to configure auto encryption/decryption behavior for a mongo.Client
+// instance.
+//
+// Automatic encryption is an enterprise only feature that only applies to operations on a collection. Automatic
+// encryption is not supported for operations on a database or view, and operations that are not bypassed will result
+// in error. Too bypass automatic encryption for all operations, set BypassAutoEncryption=true.
+//
+// Auto encryption requires the authenticated user to have the listCollections privilege action.
+//
+// If automatic encryption fails on an operation, use a MongoClient configured with bypassAutoEncryption=true and use
+// ClientEncryption.encrypt() to manually encrypt values.
+//
+// Enabling In-Use Encryption reduces the maximum document and message size (using a maxBsonObjectSize of 2MiB and
+// maxMessageSizeBytes of 6MB) and may have a negative performance impact.
+//
+// See corresponding setter methods for documentation.
+type AutoEncryptionOptions struct {
+ KeyVaultClientOptions *ClientOptions
+ KeyVaultNamespace string
+ KmsProviders map[string]map[string]any
+ SchemaMap map[string]any
+ BypassAutoEncryption *bool
+ ExtraOptions map[string]any
+ TLSConfig map[string]*tls.Config
+ HTTPClient *http.Client
+ EncryptedFieldsMap map[string]any
+ BypassQueryAnalysis *bool
+ KeyExpiration *time.Duration
+}
+
+// AutoEncryption creates a new AutoEncryptionOptions configured with default values.
+func AutoEncryption() *AutoEncryptionOptions {
+ return &AutoEncryptionOptions{
+ HTTPClient: httputil.DefaultHTTPClient,
+ }
+}
+
+// SetKeyVaultClientOptions specifies options for the client used to communicate with the key vault collection.
+//
+// If this is set, it is used to create an internal mongo.Client.
+// Otherwise, if the target mongo.Client being configured has an unlimited connection pool size (i.e. maxPoolSize=0),
+// it is reused to interact with the key vault collection.
+// Otherwise, if the target mongo.Client has a limited connection pool size, a separate internal mongo.Client is used
+// (and created if necessary). The internal mongo.Client may be shared during automatic encryption (if
+// BypassAutomaticEncryption is false). The internal mongo.Client is configured with the same options as the target
+// mongo.Client except minPoolSize is set to 0 and AutoEncryptionOptions is omitted.
+func (a *AutoEncryptionOptions) SetKeyVaultClientOptions(opts *ClientOptions) *AutoEncryptionOptions {
+ a.KeyVaultClientOptions = opts
+
+ return a
+}
+
+// SetKeyVaultNamespace specifies the namespace of the key vault collection. This is required.
+func (a *AutoEncryptionOptions) SetKeyVaultNamespace(ns string) *AutoEncryptionOptions {
+ a.KeyVaultNamespace = ns
+
+ return a
+}
+
+// SetKmsProviders specifies options for KMS providers. This is required.
+func (a *AutoEncryptionOptions) SetKmsProviders(providers map[string]map[string]any) *AutoEncryptionOptions {
+ a.KmsProviders = providers
+
+ return a
+}
+
+// SetSchemaMap specifies a map from namespace to local schema document. Schemas supplied in the schemaMap only apply
+// to configuring automatic encryption for 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.
+//
+// Supplying a schemaMap 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.
+func (a *AutoEncryptionOptions) SetSchemaMap(schemaMap map[string]any) *AutoEncryptionOptions {
+ a.SchemaMap = schemaMap
+
+ return a
+}
+
+// SetBypassAutoEncryption specifies whether or not auto encryption should be done.
+//
+// If this is unset or false and target mongo.Client being configured has an unlimited connection pool size
+// (i.e. maxPoolSize=0), it is reused in the process of auto encryption.
+// Otherwise, if the target mongo.Client has a limited connection pool size, a separate internal mongo.Client is used
+// (and created if necessary). The internal mongo.Client may be shared for key vault operations (if KeyVaultClient is
+// unset). The internal mongo.Client is configured with the same options as the target mongo.Client except minPoolSize
+// is set to 0 and AutoEncryptionOptions is omitted.
+func (a *AutoEncryptionOptions) SetBypassAutoEncryption(bypass bool) *AutoEncryptionOptions {
+ a.BypassAutoEncryption = &bypass
+
+ return a
+}
+
+// SetExtraOptions specifies a map of options to configure the mongocryptd process or mongo_crypt shared library.
+//
+// # Supported Extra Options
+//
+// "mongocryptdURI" - The mongocryptd URI. Allows setting a custom URI used to communicate with the
+// mongocryptd process. The default is "mongodb://localhost:27020", which works with the default
+// mongocryptd process spawned by the Client. Must be a string.
+//
+// "mongocryptdBypassSpawn" - If set to true, the Client will not attempt to spawn a mongocryptd
+// process. Must be a bool.
+//
+// "mongocryptdSpawnPath" - The path used when spawning mongocryptd.
+// Defaults to empty string and spawns mongocryptd from system path. Must be a string.
+//
+// "mongocryptdSpawnArgs" - Command line arguments passed when spawning mongocryptd.
+// Defaults to ["--idleShutdownTimeoutSecs=60"]. Must be an array of strings.
+//
+// "cryptSharedLibRequired" - If set to true, Client creation will return an error if the
+// crypt_shared library is not loaded. If unset or set to false, Client creation will not return an
+// error if the crypt_shared library is not loaded. The default is unset. Must be a bool.
+//
+// "cryptSharedLibPath" - The crypt_shared library override path. This must be the path to the
+// crypt_shared dynamic library file (for example, a .so, .dll, or .dylib file), not the directory
+// that contains it. If the override path is a relative path, it will be resolved relative to the
+// working directory of the process. If the override path is a relative path and the first path
+// component is the literal string "$ORIGIN", the "$ORIGIN" component will be replaced by the
+// absolute path to the directory containing the linked libmongocrypt library. Setting an override
+// path disables the default system library search path. If an override path is specified but the
+// crypt_shared library cannot be loaded, Client creation will return an error. Must be a string.
+func (a *AutoEncryptionOptions) SetExtraOptions(extraOpts map[string]any) *AutoEncryptionOptions {
+ a.ExtraOptions = extraOpts
+
+ return a
+}
+
+// SetTLSConfig specifies tls.Config instances for each KMS provider to use to configure TLS on all connections created
+// to the KMS provider.
+func (a *AutoEncryptionOptions) SetTLSConfig(cfg map[string]*tls.Config) *AutoEncryptionOptions {
+ // This should only be used to set custom TLS configurations. By default, the connection will use an empty tls.Config{} with MinVersion set to tls.VersionTLS12.
+ a.TLSConfig = cfg
+
+ return a
+}
+
+// SetEncryptedFieldsMap specifies a map from namespace to local EncryptedFieldsMap document.
+// EncryptedFieldsMap is used for Queryable Encryption.
+func (a *AutoEncryptionOptions) SetEncryptedFieldsMap(ef map[string]any) *AutoEncryptionOptions {
+ a.EncryptedFieldsMap = ef
+
+ return a
+}
+
+// SetBypassQueryAnalysis specifies whether or not query analysis should be used for automatic encryption.
+// Use this option when using explicit encryption with Queryable Encryption.
+func (a *AutoEncryptionOptions) SetBypassQueryAnalysis(bypass bool) *AutoEncryptionOptions {
+ a.BypassQueryAnalysis = &bypass
+
+ return a
+}
+
+// SetKeyExpiration specifies duration for the key expiration. 0 or negative value means "never expire".
+// The granularity is in milliseconds. Any sub-millisecond fraction will be rounded up.
+func (a *AutoEncryptionOptions) SetKeyExpiration(expiration time.Duration) *AutoEncryptionOptions {
+ a.KeyExpiration = &expiration
+
+ return a
+}
diff --git a/vendor/go.mongodb.org/mongo-driver/v2/mongo/options/bulkwriteoptions.go b/vendor/go.mongodb.org/mongo-driver/v2/mongo/options/bulkwriteoptions.go
new file mode 100644
index 0000000..cbdc7f4
--- /dev/null
+++ b/vendor/go.mongodb.org/mongo-driver/v2/mongo/options/bulkwriteoptions.go
@@ -0,0 +1,100 @@
+// Copyright (C) MongoDB, Inc. 2017-present.
+//
+// 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
+
+package options
+
+import "go.mongodb.org/mongo-driver/v2/internal/optionsutil"
+
+// DefaultOrdered is the default value for the Ordered option in BulkWriteOptions.
+var DefaultOrdered = true
+
+// BulkWriteOptions represents arguments that can be used to configure a
+// BulkWrite operation.
+//
+// See corresponding setter methods for documentation.
+type BulkWriteOptions struct {
+ BypassDocumentValidation *bool
+ Comment any
+ Ordered *bool
+ Let any
+
+ // Deprecated: This option is for internal use only and should not be set. It may be changed or removed in any
+ // release.
+ Internal optionsutil.Options
+}
+
+// BulkWriteOptionsBuilder contains options to configure bulk write operations.
+// Each option can be set through setter functions. See documentation for each
+// setter function for an explanation of the option.
+type BulkWriteOptionsBuilder struct {
+ Opts []func(*BulkWriteOptions) error
+}
+
+// BulkWrite creates a new *BulkWriteOptions instance.
+func BulkWrite() *BulkWriteOptionsBuilder {
+ opts := &BulkWriteOptionsBuilder{}
+ opts = opts.SetOrdered(DefaultOrdered)
+
+ return opts
+}
+
+// List returns a list of BulkWriteOptions setter functions.
+func (b *BulkWriteOptionsBuilder) List() []func(*BulkWriteOptions) error {
+ return b.Opts
+}
+
+// SetComment sets the value for the Comment field. Specifies a string or document that will be included in
+// server logs, profiling logs, and currentOp queries to help tracethe operation. The default value is nil,
+// which means that no comment will be included in the logs.
+func (b *BulkWriteOptionsBuilder) SetComment(comment any) *BulkWriteOptionsBuilder {
+ b.Opts = append(b.Opts, func(opts *BulkWriteOptions) error {
+ opts.Comment = comment
+
+ return nil
+ })
+
+ return b
+}
+
+// SetOrdered sets the value for the Ordered field. If true, no writes will be executed after one fails.
+// The default value is true.
+func (b *BulkWriteOptionsBuilder) SetOrdered(ordered bool) *BulkWriteOptionsBuilder {
+ b.Opts = append(b.Opts, func(opts *BulkWriteOptions) error {
+ opts.Ordered = &ordered
+
+ return nil
+ })
+
+ return b
+}
+
+// SetBypassDocumentValidation sets the value for the BypassDocumentValidation field. If true, writes
+// executed as part of the operation will opt out of document-level validation on the server. The default value is
+// false. See https://www.mongodb.com/docs/manual/core/schema-validation/ for more information about document
+// validation.
+func (b *BulkWriteOptionsBuilder) SetBypassDocumentValidation(bypass bool) *BulkWriteOptionsBuilder {
+ b.Opts = append(b.Opts, func(opts *BulkWriteOptions) error {
+ opts.BypassDocumentValidation = &bypass
+
+ return nil
+ })
+
+ return b
+}
+
+// SetLet sets the value for the Let field. Let specifies parameters for all update and delete commands in the BulkWrite.
+// This option is only valid for MongoDB versions >= 5.0. Older servers will report an error for using this option.
+// This must be a document mapping parameter names to values. 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").
+func (b *BulkWriteOptionsBuilder) SetLet(let any) *BulkWriteOptionsBuilder {
+ b.Opts = append(b.Opts, func(opts *BulkWriteOptions) error {
+ opts.Let = &let
+
+ return nil
+ })
+
+ return b
+}
diff --git a/vendor/go.mongodb.org/mongo-driver/v2/mongo/options/changestreamoptions.go b/vendor/go.mongodb.org/mongo-driver/v2/mongo/options/changestreamoptions.go
new file mode 100644
index 0000000..92f1963
--- /dev/null
+++ b/vendor/go.mongodb.org/mongo-driver/v2/mongo/options/changestreamoptions.go
@@ -0,0 +1,183 @@
+// Copyright (C) MongoDB, Inc. 2017-present.
+//
+// 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
+
+package options
+
+import (
+ "time"
+
+ "go.mongodb.org/mongo-driver/v2/bson"
+)
+
+// ChangeStreamOptions represents arguments that can be used to configure a Watch operation.
+//
+// See corresponding setter methods for documentation.
+type ChangeStreamOptions struct {
+ BatchSize *int32
+ Collation *Collation
+ Comment any
+ FullDocument *FullDocument
+ FullDocumentBeforeChange *FullDocument
+ MaxAwaitTime *time.Duration
+ ResumeAfter any
+ ShowExpandedEvents *bool
+ StartAtOperationTime *bson.Timestamp
+ StartAfter any
+ Custom bson.M
+ CustomPipeline bson.M
+}
+
+// ChangeStreamOptionsBuilder contains options to configure change stream
+// operations. Each option can be set through setter functions. See
+// documentation for each setter function for an explanation of the option.
+type ChangeStreamOptionsBuilder struct {
+ Opts []func(*ChangeStreamOptions) error
+}
+
+// ChangeStream creates a new ChangeStreamOptions instance.
+func ChangeStream() *ChangeStreamOptionsBuilder {
+ return &ChangeStreamOptionsBuilder{}
+}
+
+// List returns a list of ChangeStreamOptions setter functions.
+func (cso *ChangeStreamOptionsBuilder) List() []func(*ChangeStreamOptions) error {
+ return cso.Opts
+}
+
+// SetBatchSize sets the value for the BatchSize field. Specifies the maximum number of documents to
+// be included in each batch returned by the server.
+func (cso *ChangeStreamOptionsBuilder) SetBatchSize(i int32) *ChangeStreamOptionsBuilder {
+ cso.Opts = append(cso.Opts, func(opts *ChangeStreamOptions) error {
+ opts.BatchSize = &i
+ return nil
+ })
+ return cso
+}
+
+// SetCollation sets the value for the Collation field. Specifies a collation to
+// use for string comparisons during the operation. The default value is nil,
+// which means the default collation of the collection will be used.
+func (cso *ChangeStreamOptionsBuilder) SetCollation(c Collation) *ChangeStreamOptionsBuilder {
+ cso.Opts = append(cso.Opts, func(opts *ChangeStreamOptions) error {
+ opts.Collation = &c
+ return nil
+ })
+ return cso
+}
+
+// SetComment sets the value for the Comment field. Specifies a string or document that will be included in
+// server logs, profiling logs, and currentOp queries to help trace the operation. The default is nil,
+// which means that no comment will be included in the logs.
+func (cso *ChangeStreamOptionsBuilder) SetComment(comment any) *ChangeStreamOptionsBuilder {
+ cso.Opts = append(cso.Opts, func(opts *ChangeStreamOptions) error {
+ opts.Comment = comment
+ return nil
+ })
+ return cso
+}
+
+// SetFullDocument sets the value for the FullDocument field. Specifies how the updated document should be
+// returned in change notifications for update operations. The default is options.Default, which means that
+// only partial update deltas will be included in the change notification.
+func (cso *ChangeStreamOptionsBuilder) SetFullDocument(fd FullDocument) *ChangeStreamOptionsBuilder {
+ cso.Opts = append(cso.Opts, func(opts *ChangeStreamOptions) error {
+ opts.FullDocument = &fd
+ return nil
+ })
+ return cso
+}
+
+// SetFullDocumentBeforeChange sets the value for the FullDocumentBeforeChange field. Specifies how the
+// pre-update document should be returned in change notifications for update operations. The default
+// is options.Off, which means that the pre-update document will not be included in the change notification.
+func (cso *ChangeStreamOptionsBuilder) SetFullDocumentBeforeChange(fdbc FullDocument) *ChangeStreamOptionsBuilder {
+ cso.Opts = append(cso.Opts, func(opts *ChangeStreamOptions) error {
+ opts.FullDocumentBeforeChange = &fdbc
+ return nil
+ })
+ return cso
+}
+
+// SetMaxAwaitTime sets the value for the MaxAwaitTime field. The maximum amount of time that the server should
+// wait for new documents to satisfy a tailable cursor query.
+func (cso *ChangeStreamOptionsBuilder) SetMaxAwaitTime(d time.Duration) *ChangeStreamOptionsBuilder {
+ cso.Opts = append(cso.Opts, func(opts *ChangeStreamOptions) error {
+ opts.MaxAwaitTime = &d
+ return nil
+ })
+ return cso
+}
+
+// SetResumeAfter sets the value for the ResumeAfter field. Specifies a document specifying the logical starting
+// point for the change stream. Only changes corresponding to an oplog entry immediately after the resume token
+// will be returned. If this is specified, StartAtOperationTime and StartAfter must not be set.
+func (cso *ChangeStreamOptionsBuilder) SetResumeAfter(rt any) *ChangeStreamOptionsBuilder {
+ cso.Opts = append(cso.Opts, func(opts *ChangeStreamOptions) error {
+ opts.ResumeAfter = rt
+ return nil
+ })
+ return cso
+}
+
+// SetShowExpandedEvents sets the value for the ShowExpandedEvents field. ShowExpandedEvents specifies whether
+// the server will return an expanded list of change stream events. Additional events include: createIndexes,
+// dropIndexes, modify, create, shardCollection, reshardCollection and refineCollectionShardKey. This option
+// is only valid for MongoDB versions >= 6.0.
+func (cso *ChangeStreamOptionsBuilder) SetShowExpandedEvents(see bool) *ChangeStreamOptionsBuilder {
+ cso.Opts = append(cso.Opts, func(opts *ChangeStreamOptions) error {
+ opts.ShowExpandedEvents = &see
+ return nil
+ })
+ return cso
+}
+
+// SetStartAtOperationTime sets the value for the StartAtOperationTime field. If specified, the change stream
+// will only return changes that occurred at or after the given timestamp.
+// If this is specified, ResumeAfter and StartAfter must not be set.
+func (cso *ChangeStreamOptionsBuilder) SetStartAtOperationTime(t *bson.Timestamp) *ChangeStreamOptionsBuilder {
+ cso.Opts = append(cso.Opts, func(opts *ChangeStreamOptions) error {
+ opts.StartAtOperationTime = t
+ return nil
+ })
+ return cso
+}
+
+// SetStartAfter sets the value for the StartAfter field. Sets a document specifying the logical starting
+// point for the change stream. This is similar to the ResumeAfter option, but allows a resume token from
+// an "invalidate" notification to be used. This allows a change stream on a collection to be resumed after
+// the collection has been dropped and recreated or renamed. Only changes corresponding to an oplog entry
+// immediately after the specified token will be returned. If this is specified, ResumeAfter and
+// StartAtOperationTime must not be set. This option is only valid for MongoDB versions >= 4.1.1.
+func (cso *ChangeStreamOptionsBuilder) SetStartAfter(sa any) *ChangeStreamOptionsBuilder {
+ cso.Opts = append(cso.Opts, func(opts *ChangeStreamOptions) error {
+ opts.StartAfter = sa
+ return nil
+ })
+ return cso
+}
+
+// SetCustom sets the value for the Custom field. Key-value pairs of the BSON map should correlate
+// with desired option names and values. Values must be Marshalable. Custom options may conflict
+// with non-custom options, and custom options bypass client-side validation. Prefer using non-custom
+// options where possible.
+func (cso *ChangeStreamOptionsBuilder) SetCustom(c bson.M) *ChangeStreamOptionsBuilder {
+ cso.Opts = append(cso.Opts, func(opts *ChangeStreamOptions) error {
+ opts.Custom = c
+ return nil
+ })
+ return cso
+}
+
+// SetCustomPipeline sets the value for the CustomPipeline field. Key-value pairs of the BSON map
+// should correlate with desired option names and values. Values must be Marshalable. Custom pipeline
+// options bypass client-side validation. Prefer using non-custom options where possible.
+func (cso *ChangeStreamOptionsBuilder) SetCustomPipeline(cp bson.M) *ChangeStreamOptionsBuilder {
+ cso.Opts = append(cso.Opts, func(opts *ChangeStreamOptions) error {
+ opts.CustomPipeline = cp
+ return nil
+ })
+ return cso
+}
diff --git a/vendor/go.mongodb.org/mongo-driver/v2/mongo/options/clientbulkwriteoptions.go b/vendor/go.mongodb.org/mongo-driver/v2/mongo/options/clientbulkwriteoptions.go
new file mode 100644
index 0000000..90d6c24
--- /dev/null
+++ b/vendor/go.mongodb.org/mongo-driver/v2/mongo/options/clientbulkwriteoptions.go
@@ -0,0 +1,127 @@
+// Copyright (C) MongoDB, Inc. 2024-present.
+//
+// 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
+
+package options
+
+import (
+ "go.mongodb.org/mongo-driver/v2/internal/optionsutil"
+ "go.mongodb.org/mongo-driver/v2/mongo/writeconcern"
+)
+
+// ClientBulkWriteOptions represents options that can be used to configure a client-level BulkWrite operation.
+//
+// See corresponding setter methods for documentation.
+type ClientBulkWriteOptions struct {
+ BypassDocumentValidation *bool
+ Comment any
+ Ordered *bool
+ Let any
+ WriteConcern *writeconcern.WriteConcern
+ VerboseResults *bool
+
+ // Deprecated: This option is for internal use only and should not be set. It may be changed or removed in any
+ // release.
+ Internal optionsutil.Options
+}
+
+// ClientBulkWriteOptionsBuilder contains options to configure client-level bulk
+// write operations.
+// Each option can be set through setter functions. See documentation for each
+// setter function for an explanation of the option.
+type ClientBulkWriteOptionsBuilder struct {
+ Opts []func(*ClientBulkWriteOptions) error
+}
+
+// ClientBulkWrite creates a new *ClientBulkWriteOptions instance.
+func ClientBulkWrite() *ClientBulkWriteOptionsBuilder {
+ opts := &ClientBulkWriteOptionsBuilder{}
+ opts = opts.SetOrdered(DefaultOrdered)
+
+ return opts
+}
+
+// List returns a list of ClientBulkWriteOptions setter functions.
+func (b *ClientBulkWriteOptionsBuilder) List() []func(*ClientBulkWriteOptions) error {
+ return b.Opts
+}
+
+// SetComment sets the value for the Comment field. Specifies a string or document that will be included in
+// server logs, profiling logs, and currentOp queries to help tracethe operation. The default value is nil,
+// which means that no comment will be included in the logs.
+func (b *ClientBulkWriteOptionsBuilder) SetComment(comment any) *ClientBulkWriteOptionsBuilder {
+ b.Opts = append(b.Opts, func(opts *ClientBulkWriteOptions) error {
+ opts.Comment = comment
+
+ return nil
+ })
+
+ return b
+}
+
+// SetOrdered sets the value for the Ordered field. If true, no writes will be executed after one fails.
+// The default value is true.
+func (b *ClientBulkWriteOptionsBuilder) SetOrdered(ordered bool) *ClientBulkWriteOptionsBuilder {
+ b.Opts = append(b.Opts, func(opts *ClientBulkWriteOptions) error {
+ opts.Ordered = &ordered
+
+ return nil
+ })
+
+ return b
+}
+
+// SetBypassDocumentValidation sets the value for the BypassDocumentValidation field. If true, writes
+// executed as part of the operation will opt out of document-level validation on the server. The default
+// value is false. See https://www.mongodb.com/docs/manual/core/schema-validation/ for more information
+// about document validation.
+func (b *ClientBulkWriteOptionsBuilder) SetBypassDocumentValidation(bypass bool) *ClientBulkWriteOptionsBuilder {
+ b.Opts = append(b.Opts, func(opts *ClientBulkWriteOptions) error {
+ opts.BypassDocumentValidation = &bypass
+
+ return nil
+ })
+
+ return b
+}
+
+// SetLet sets the value for the Let field. Let specifies parameters for all update and delete commands in the BulkWrite.
+// This must be a document mapping parameter names to values. 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").
+func (b *ClientBulkWriteOptionsBuilder) SetLet(let any) *ClientBulkWriteOptionsBuilder {
+ b.Opts = append(b.Opts, func(opts *ClientBulkWriteOptions) error {
+ opts.Let = &let
+
+ return nil
+ })
+
+ return b
+}
+
+// SetWriteConcern sets the value for the WriteConcern field. Specifies the write concern for
+// operations in the transaction. The default value is nil, which means that the default
+// write concern of the session used to start the transaction will be used.
+func (b *ClientBulkWriteOptionsBuilder) SetWriteConcern(wc *writeconcern.WriteConcern) *ClientBulkWriteOptionsBuilder {
+ b.Opts = append(b.Opts, func(opts *ClientBulkWriteOptions) error {
+ opts.WriteConcern = wc
+
+ return nil
+ })
+
+ return b
+}
+
+// SetVerboseResults sets the value for the VerboseResults field. Specifies whether detailed
+// results for each successful operation should be included in the returned BulkWriteResult.
+// The defaults value is false.
+func (b *ClientBulkWriteOptionsBuilder) SetVerboseResults(verboseResults bool) *ClientBulkWriteOptionsBuilder {
+ b.Opts = append(b.Opts, func(opts *ClientBulkWriteOptions) error {
+ opts.VerboseResults = &verboseResults
+
+ return nil
+ })
+
+ return b
+}
diff --git a/vendor/go.mongodb.org/mongo-driver/v2/mongo/options/clientencryptionoptions.go b/vendor/go.mongodb.org/mongo-driver/v2/mongo/options/clientencryptionoptions.go
new file mode 100644
index 0000000..a6c477a
--- /dev/null
+++ b/vendor/go.mongodb.org/mongo-driver/v2/mongo/options/clientencryptionoptions.go
@@ -0,0 +1,158 @@
+// Copyright (C) MongoDB, Inc. 2017-present.
+//
+// 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
+
+package options
+
+import (
+ "crypto/tls"
+ "fmt"
+ "net/http"
+ "time"
+
+ "go.mongodb.org/mongo-driver/v2/internal/httputil"
+)
+
+// ClientEncryptionOptions represents all possible arguments used to configure a ClientEncryption instance.
+//
+// See corresponding setter methods for documentation.
+type ClientEncryptionOptions struct {
+ KeyVaultNamespace string
+ KmsProviders map[string]map[string]any
+ TLSConfig map[string]*tls.Config
+ HTTPClient *http.Client
+ KeyExpiration *time.Duration
+}
+
+// ClientEncryptionOptionsBuilder contains options to configure client
+// encryption operations. Each option can be set through setter functions. See
+// documentation for each setter function for an explanation of the option.
+type ClientEncryptionOptionsBuilder struct {
+ Opts []func(*ClientEncryptionOptions) error
+}
+
+// ClientEncryption creates a new ClientEncryptionOptions instance.
+func ClientEncryption() *ClientEncryptionOptionsBuilder {
+ return &ClientEncryptionOptionsBuilder{
+ Opts: []func(*ClientEncryptionOptions) error{
+ func(arg *ClientEncryptionOptions) error {
+ arg.HTTPClient = httputil.DefaultHTTPClient
+ return nil
+ },
+ },
+ }
+}
+
+// List returns a list of ClientEncryptionOptions setter functions.
+func (c *ClientEncryptionOptionsBuilder) List() []func(*ClientEncryptionOptions) error {
+ return c.Opts
+}
+
+// SetKeyVaultNamespace specifies the namespace of the key vault collection. This is required.
+func (c *ClientEncryptionOptionsBuilder) SetKeyVaultNamespace(ns string) *ClientEncryptionOptionsBuilder {
+ c.Opts = append(c.Opts, func(opts *ClientEncryptionOptions) error {
+ opts.KeyVaultNamespace = ns
+ return nil
+ })
+ return c
+}
+
+// SetKmsProviders specifies options for KMS providers. This is required.
+func (c *ClientEncryptionOptionsBuilder) SetKmsProviders(providers map[string]map[string]any) *ClientEncryptionOptionsBuilder {
+ c.Opts = append(c.Opts, func(opts *ClientEncryptionOptions) error {
+ opts.KmsProviders = providers
+ return nil
+ })
+ return c
+}
+
+// SetTLSConfig specifies tls.Config instances for each KMS provider to use to configure TLS on all connections created
+// to the KMS provider.
+//
+// This should only be used to set custom TLS configurations. By default, the connection will use an empty tls.Config{} with MinVersion set to tls.VersionTLS12.
+func (c *ClientEncryptionOptionsBuilder) SetTLSConfig(cfg map[string]*tls.Config) *ClientEncryptionOptionsBuilder {
+ c.Opts = append(c.Opts, func(opts *ClientEncryptionOptions) error {
+ opts.TLSConfig = cfg
+
+ return nil
+ })
+
+ return c
+}
+
+// SetKeyExpiration specifies duration for the key expiration. 0 or negative value means "never expire".
+// The granularity is in milliseconds. Any sub-millisecond fraction will be rounded up.
+func (c *ClientEncryptionOptionsBuilder) SetKeyExpiration(expiration time.Duration) *ClientEncryptionOptionsBuilder {
+ c.Opts = append(c.Opts, func(opts *ClientEncryptionOptions) error {
+ opts.KeyExpiration = &expiration
+
+ return nil
+ })
+
+ return c
+}
+
+// BuildTLSConfig specifies tls.Config options for each KMS provider to use to configure TLS on all connections created
+// to the KMS provider. The input map should contain a mapping from each KMS provider to a document containing the necessary
+// options, as follows:
+//
+// {
+// "kmip": {
+// "tlsCertificateKeyFile": "foo.pem",
+// "tlsCAFile": "fooCA.pem"
+// }
+// }
+//
+// Currently, the following TLS options are supported:
+//
+// 1. "tlsCertificateKeyFile" (or "sslClientCertificateKeyFile"): The "tlsCertificateKeyFile" option specifies a path to
+// the client certificate and private key, which must be concatenated into one file.
+//
+// 2. "tlsCertificateKeyFilePassword" (or "sslClientCertificateKeyPassword"): Specify the password to decrypt the client
+// private key file (e.g. "tlsCertificateKeyFilePassword=password").
+//
+// 3. "tlsCaFile" (or "sslCertificateAuthorityFile"): Specify the path to a single or bundle of certificate authorities
+// to be considered trusted when making a TLS connection (e.g. "tlsCaFile=/path/to/caFile").
+//
+// This should only be used to set custom TLS options. By default, the connection will use an empty tls.Config{} with MinVersion set to tls.VersionTLS12.
+func BuildTLSConfig(tlsOpts map[string]any) (*tls.Config, error) {
+ // use TLS min version 1.2 to enforce more secure hash algorithms and advanced cipher suites
+ cfg := &tls.Config{MinVersion: tls.VersionTLS12}
+
+ for name := range tlsOpts {
+ var err error
+ switch name {
+ case "tlsCertificateKeyFile", "sslClientCertificateKeyFile":
+ clientCertPath, ok := tlsOpts[name].(string)
+ if !ok {
+ return nil, fmt.Errorf("expected %q value to be of type string, got %T", name, tlsOpts[name])
+ }
+ // apply custom key file password if found, otherwise use empty string
+ if keyPwd, found := tlsOpts["tlsCertificateKeyFilePassword"].(string); found {
+ _, err = addClientCertFromConcatenatedFile(cfg, clientCertPath, keyPwd)
+ } else if keyPwd, found := tlsOpts["sslClientCertificateKeyPassword"].(string); found {
+ _, err = addClientCertFromConcatenatedFile(cfg, clientCertPath, keyPwd)
+ } else {
+ _, err = addClientCertFromConcatenatedFile(cfg, clientCertPath, "")
+ }
+ case "tlsCertificateKeyFilePassword", "sslClientCertificateKeyPassword":
+ continue
+ case "tlsCAFile", "sslCertificateAuthorityFile":
+ caPath, ok := tlsOpts[name].(string)
+ if !ok {
+ return nil, fmt.Errorf("expected %q value to be of type string, got %T", name, tlsOpts[name])
+ }
+ err = addCACertFromFile(cfg, caPath)
+ default:
+ return nil, fmt.Errorf("unrecognized TLS option %v", name)
+ }
+
+ if err != nil {
+ return nil, err
+ }
+ }
+
+ return cfg, nil
+}
diff --git a/vendor/go.mongodb.org/mongo-driver/v2/mongo/options/clientoptions.go b/vendor/go.mongodb.org/mongo-driver/v2/mongo/options/clientoptions.go
new file mode 100644
index 0000000..6a69b2e
--- /dev/null
+++ b/vendor/go.mongodb.org/mongo-driver/v2/mongo/options/clientoptions.go
@@ -0,0 +1,1368 @@
+// Copyright (C) MongoDB, Inc. 2017-present.
+//
+// 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
+
+package options
+
+import (
+ "bytes"
+ "context"
+ "crypto/tls"
+ "crypto/x509"
+ "encoding/pem"
+ "errors"
+ "fmt"
+ "io/ioutil"
+ "math"
+ "net"
+ "net/http"
+ "reflect"
+ "strings"
+ "time"
+
+ "github.com/youmark/pkcs8"
+ "go.mongodb.org/mongo-driver/v2/bson"
+ "go.mongodb.org/mongo-driver/v2/event"
+ "go.mongodb.org/mongo-driver/v2/internal/httputil"
+ "go.mongodb.org/mongo-driver/v2/internal/optionsutil"
+ "go.mongodb.org/mongo-driver/v2/mongo/readconcern"
+ "go.mongodb.org/mongo-driver/v2/mongo/readpref"
+ "go.mongodb.org/mongo-driver/v2/mongo/writeconcern"
+ "go.mongodb.org/mongo-driver/v2/tag"
+ "go.mongodb.org/mongo-driver/v2/x/mongo/driver"
+ "go.mongodb.org/mongo-driver/v2/x/mongo/driver/auth"
+ "go.mongodb.org/mongo-driver/v2/x/mongo/driver/connstring"
+ "go.mongodb.org/mongo-driver/v2/x/mongo/driver/wiremessage"
+)
+
+const (
+ // ServerMonitoringModeAuto indicates that the client will behave like "poll"
+ // mode when running on a FaaS (Function as a Service) platform, or like
+ // "stream" mode otherwise. The client detects its execution environment by
+ // following the rules for generating the "client.env" handshake metadata field
+ // as specified in the MongoDB Handshake specification. This is the default
+ // mode.
+ ServerMonitoringModeAuto = connstring.ServerMonitoringModeAuto
+
+ // ServerMonitoringModePoll indicates that the client will periodically check
+ // the server using a hello or legacy hello command and then sleep for
+ // heartbeatFrequencyMS milliseconds before running another check.
+ ServerMonitoringModePoll = connstring.ServerMonitoringModePoll
+
+ // ServerMonitoringModeStream indicates that the client will use a streaming
+ // protocol when the server supports it. The streaming protocol optimally
+ // reduces the time it takes for a client to discover server state changes.
+ ServerMonitoringModeStream = connstring.ServerMonitoringModeStream
+)
+
+// ContextDialer is an interface that can be implemented by types that can create connections. It should be used to
+// provide a custom dialer when configuring a Client.
+//
+// DialContext should return a connection to the provided address on the given network.
+type ContextDialer interface {
+ DialContext(ctx context.Context, network, address string) (net.Conn, error)
+}
+
+// Credential can be used to provide authentication options when configuring a Client.
+//
+// AuthMechanism: the mechanism to use for authentication. Supported values include "SCRAM-SHA-256", "SCRAM-SHA-1",
+// "PLAIN", "GSSAPI", "MONGODB-X509", and "MONGODB-AWS". This can also be set through the "authMechanism"
+// URI option. (e.g. "authMechanism=PLAIN"). For more information, see
+// https://www.mongodb.com/docs/manual/core/authentication-mechanisms/.
+//
+// AuthMechanismProperties can be used to specify additional configuration options for certain mechanisms. They can also
+// be set through the "authMechanismProperites" URI option
+// (e.g. "authMechanismProperties=SERVICE_NAME:service,CANONICALIZE_HOST_NAME:true"). Supported properties are:
+//
+// 1. SERVICE_NAME: The service name to use for GSSAPI authentication. The default is "mongodb".
+//
+// 2. CANONICALIZE_HOST_NAME: If "true", the driver will canonicalize the host name for GSSAPI authentication. The default
+// is "false".
+//
+// 3. SERVICE_REALM: The service realm for GSSAPI authentication.
+//
+// 4. SERVICE_HOST: The host name to use for GSSAPI authentication. This should be specified if the host name to use for
+// authentication is different than the one given for Client construction.
+//
+// 4. AWS_SESSION_TOKEN: The AWS token for MONGODB-AWS authentication. This is optional and used for authentication with
+// temporary credentials.
+//
+// The SERVICE_HOST and CANONICALIZE_HOST_NAME properties must not be used at the same time on Linux and Darwin
+// systems.
+//
+// AuthSource: the name of the database to use for authentication. This defaults to "$external" for MONGODB-AWS,
+// MONGODB-OIDC, MONGODB-X509, GSSAPI, and PLAIN. It defaults to "admin" for all other auth mechanisms. This can
+// also be set through the "authSource" URI option (e.g. "authSource=otherDb").
+//
+// Username: the username for authentication. This can also be set through the URI as a username:password pair before
+// the first @ character. For example, a URI for user "user", password "pwd", and host "localhost:27017" would be
+// "mongodb://user:pwd@localhost:27017". This is optional for X509 authentication and will be extracted from the
+// client certificate if not specified.
+//
+// Password: the password for authentication. This must not be specified for X509 and is optional for GSSAPI
+// authentication.
+//
+// PasswordSet: For GSSAPI, this must be true if a password is specified, even if the password is the empty string, and
+// false if no password is specified, indicating that the password should be taken from the context of the running
+// process. For other mechanisms, this field is ignored.
+type Credential struct {
+ AuthMechanism string
+ AuthMechanismProperties map[string]string
+ AuthSource string
+ Username string
+ Password string
+ PasswordSet bool
+ OIDCMachineCallback OIDCCallback
+ OIDCHumanCallback OIDCCallback
+}
+
+// OIDCCallback is the type for both Human and Machine Callback flows.
+// RefreshToken will always be nil in the OIDCArgs for the Machine flow.
+type OIDCCallback func(context.Context, *OIDCArgs) (*OIDCCredential, error)
+
+// OIDCArgs contains the arguments for the OIDC callback.
+type OIDCArgs struct {
+ Version int
+ IDPInfo *IDPInfo
+ RefreshToken *string
+}
+
+// OIDCCredential contains the access token and refresh token.
+type OIDCCredential struct {
+ AccessToken string
+ ExpiresAt *time.Time
+ RefreshToken *string
+}
+
+// IDPInfo contains the information needed to perform OIDC authentication with
+// an Identity Provider.
+type IDPInfo struct {
+ Issuer string
+ ClientID string
+ RequestScopes []string
+}
+
+// BSONOptions are optional BSON marshaling and unmarshaling behaviors.
+type BSONOptions struct {
+ // UseJSONStructTags causes the driver to fall back to using the "json"
+ // struct tag if a "bson" struct tag is not specified.
+ UseJSONStructTags bool
+
+ // ErrorOnInlineDuplicates causes the driver to return an error if there is
+ // a duplicate field in the marshaled BSON when the "inline" struct tag
+ // option is set.
+ ErrorOnInlineDuplicates bool
+
+ // IntMinSize causes the driver to marshal Go integer values (int, int8,
+ // int16, int32, int64, uint, uint8, uint16, uint32, or uint64) as the
+ // minimum BSON int size (either 32 or 64 bits) that can represent the
+ // integer value.
+ IntMinSize bool
+
+ // NilMapAsEmpty causes the driver to marshal nil Go maps as empty BSON
+ // documents instead of BSON null.
+ //
+ // Empty BSON documents take up slightly more space than BSON null, but
+ // preserve the ability to use document update operations like "$set" that
+ // do not work on BSON null.
+ NilMapAsEmpty bool
+
+ // NilSliceAsEmpty causes the driver to marshal nil Go slices as empty BSON
+ // arrays instead of BSON null.
+ //
+ // Empty BSON arrays take up slightly more space than BSON null, but
+ // preserve the ability to use array update operations like "$push" or
+ // "$addToSet" that do not work on BSON null.
+ NilSliceAsEmpty bool
+
+ // NilByteSliceAsEmpty causes the driver to marshal nil Go byte slices as
+ // empty BSON binary values instead of BSON null.
+ NilByteSliceAsEmpty bool
+
+ // OmitZeroStruct causes the driver to consider the zero value for a struct
+ // (e.g. MyStruct{}) as empty and omit it from the marshaled BSON when the
+ // "omitempty" struct tag option or the "OmitEmpty" field is set.
+ OmitZeroStruct bool
+
+ // OmitEmpty causes the driver to omit empty values from the marshaled BSON.
+ OmitEmpty bool
+
+ // StringifyMapKeysWithFmt causes the driver to convert Go map keys to BSON
+ // document field name strings using fmt.Sprint instead of the default
+ // string conversion logic.
+ StringifyMapKeysWithFmt bool
+
+ // AllowTruncatingDoubles causes the driver to truncate the fractional part
+ // of BSON "double" values when attempting to unmarshal them into a Go
+ // integer (int, int8, int16, int32, or int64) struct field. The truncation
+ // logic does not apply to BSON "decimal128" values.
+ AllowTruncatingDoubles bool
+
+ // BinaryAsSlice causes the driver to unmarshal BSON binary field values
+ // that are the "Generic" or "Old" BSON binary subtype as a Go byte slice
+ // instead of a bson.Binary.
+ BinaryAsSlice bool
+
+ // DefaultDocumentM causes the driver to always unmarshal documents into the
+ // bson.M type. This behavior is restricted to data typed as
+ // "any" or "map[string]any".
+ DefaultDocumentM bool
+
+ // DefaultDocumentMap causes the driver to always unmarshal documents into the
+ // map[string]any type. This behavior is restricted to data typed as "any" or
+ // "map[string]any".
+ DefaultDocumentMap bool
+
+ // ObjectIDAsHexString causes the Decoder to decode object IDs to their hex
+ // representation.
+ ObjectIDAsHexString bool
+
+ // UseLocalTimeZone causes the driver to unmarshal time.Time values in the
+ // local timezone instead of the UTC timezone.
+ UseLocalTimeZone bool
+
+ // ZeroMaps causes the driver to delete any existing values from Go maps in
+ // the destination value before unmarshaling BSON documents into them.
+ ZeroMaps bool
+
+ // ZeroStructs causes the driver to delete any existing values from Go
+ // structs in the destination value before unmarshaling BSON documents into
+ // them.
+ ZeroStructs bool
+}
+
+// DriverInfo appends the client metadata generated by the driver when
+// handshaking the server. These options do not replace the values used
+// during the handshake, rather they are deliminated with a | with the
+// driver-generated data. This should be used by libraries wrapping the driver,
+// e.g. ODMs.
+type DriverInfo struct {
+ Name string // Name of the library wrapping the driver.
+ Version string // Version of the library wrapping the driver.
+ Platform string // Platform information for the wrapping driver.
+}
+
+// ClientOptions contains arguments to configure a Client instance. Arguments
+// can be set through the ClientOptions setter functions. See each function for
+// documentation.
+type ClientOptions struct {
+ AppName *string
+ Auth *Credential
+ AutoEncryptionOptions *AutoEncryptionOptions
+ ConnectTimeout *time.Duration
+ Compressors []string
+ Dialer ContextDialer
+ Direct *bool
+ DisableOCSPEndpointCheck *bool
+ DriverInfo *DriverInfo
+ HeartbeatInterval *time.Duration
+ Hosts []string
+ HTTPClient *http.Client
+ LoadBalanced *bool
+ LocalThreshold *time.Duration
+ LoggerOptions *LoggerOptions
+ MaxConnIdleTime *time.Duration
+ MaxPoolSize *uint64
+ MinPoolSize *uint64
+ MaxConnecting *uint64
+ PoolMonitor *event.PoolMonitor
+ Monitor *event.CommandMonitor
+ ServerMonitor *event.ServerMonitor
+ ReadConcern *readconcern.ReadConcern
+ ReadPreference *readpref.ReadPref
+ BSONOptions *BSONOptions
+ Registry *bson.Registry
+ ReplicaSet *string
+ RetryReads *bool
+ RetryWrites *bool
+ ServerAPIOptions *ServerAPIOptions
+ ServerMonitoringMode *string
+ ServerSelectionTimeout *time.Duration
+ SRVMaxHosts *int
+ SRVServiceName *string
+ Timeout *time.Duration
+ TLSConfig *tls.Config
+ WriteConcern *writeconcern.WriteConcern
+ ZlibLevel *int
+ ZstdLevel *int
+
+ MaxAdaptiveRetries *uint
+ EnableOverloadRetargeting *bool
+
+ // Crypt specifies a custom driver.Crypt to be used to encrypt and decrypt documents. The default is no
+ // encryption.
+ //
+ // Deprecated: This option is for internal use only and should not be set (see GODRIVER-2149). It may be
+ // changed in any release. This option will be removed in 3.0 and replaced with the Custom options.Options
+ // pattern: SetInternalClientOptions(clientOptions, "crypt", myCrypt)
+ Crypt driver.Crypt
+
+ // Deployment specifies a custom deployment to use for the new Client.
+ //
+ // Deprecated: This option is for internal use only and should not be set. It may be changed in any release.
+ // This option will be removed in 3.0 and replaced with the Custom options.Options pattern:
+ // SetInternalClientOptions(clientOptions, "deployment", myDeployment)
+ Deployment driver.Deployment
+
+ // Custom specifies internal options for the new Client.
+ //
+ // Deprecated: This option is for internal use only and should not be set. It may be changed or removed in any
+ // release.
+ Custom optionsutil.Options
+
+ connString *connstring.ConnString
+ err error
+}
+
+// Client creates a new ClientOptions instance.
+func Client() *ClientOptions {
+ opts := &ClientOptions{}
+ opts = opts.SetHTTPClient(httputil.NewHTTPClient())
+
+ return opts
+}
+
+func setURIOpts(uri string, opts *ClientOptions) error {
+ connString, err := connstring.ParseAndValidate(uri)
+ if err != nil {
+ return err
+ }
+
+ opts.connString = connString
+
+ if connString.AppName != "" {
+ opts.AppName = &connString.AppName
+ }
+
+ // Only create a Credential if there is a request for authentication via
+ // non-empty credentials in the URI.
+ if connString.HasAuthParameters() {
+ opts.Auth = &Credential{
+ AuthMechanism: connString.AuthMechanism,
+ AuthMechanismProperties: connString.AuthMechanismProperties,
+ AuthSource: connString.AuthSource,
+ Username: connString.Username,
+ Password: connString.Password,
+ PasswordSet: connString.PasswordSet,
+ }
+ }
+
+ if connString.ConnectSet {
+ direct := connString.Connect == connstring.SingleConnect
+ opts.Direct = &direct
+ }
+
+ if connString.DirectConnectionSet {
+ opts.Direct = &connString.DirectConnection
+ }
+
+ if connString.ConnectTimeoutSet {
+ opts.ConnectTimeout = &connString.ConnectTimeout
+ }
+
+ if len(connString.Compressors) > 0 {
+ opts.Compressors = connString.Compressors
+ if stringSliceContains(opts.Compressors, "zlib") {
+ defaultLevel := wiremessage.DefaultZlibLevel
+ opts.ZlibLevel = &defaultLevel
+ }
+ if stringSliceContains(opts.Compressors, "zstd") {
+ defaultLevel := wiremessage.DefaultZstdLevel
+ opts.ZstdLevel = &defaultLevel
+ }
+ }
+
+ if connString.HeartbeatIntervalSet {
+ opts.HeartbeatInterval = &connString.HeartbeatInterval
+ }
+
+ opts.Hosts = connString.Hosts
+
+ if connString.LoadBalancedSet {
+ opts.LoadBalanced = &connString.LoadBalanced
+ }
+
+ if connString.LocalThresholdSet {
+ opts.LocalThreshold = &connString.LocalThreshold
+ }
+
+ if connString.MaxConnIdleTimeSet {
+ opts.MaxConnIdleTime = &connString.MaxConnIdleTime
+ }
+
+ if connString.MaxPoolSizeSet {
+ opts.MaxPoolSize = &connString.MaxPoolSize
+ }
+
+ if connString.MinPoolSizeSet {
+ opts.MinPoolSize = &connString.MinPoolSize
+ }
+
+ if connString.MaxConnectingSet {
+ opts.MaxConnecting = &connString.MaxConnecting
+ }
+
+ if connString.ReadConcernLevel != "" {
+ opts.ReadConcern = &readconcern.ReadConcern{Level: connString.ReadConcernLevel}
+ }
+
+ if connString.ReadPreference != "" || len(connString.ReadPreferenceTagSets) > 0 || connString.MaxStalenessSet {
+ readPrefOpts := make([]readpref.Option, 0, 1)
+
+ tagSets := tag.NewTagSetsFromMaps(connString.ReadPreferenceTagSets)
+ if len(tagSets) > 0 {
+ readPrefOpts = append(readPrefOpts, readpref.WithTagSets(tagSets...))
+ }
+
+ if connString.MaxStaleness != 0 {
+ readPrefOpts = append(readPrefOpts, readpref.WithMaxStaleness(connString.MaxStaleness))
+ }
+
+ mode, err := readpref.ModeFromString(connString.ReadPreference)
+ if err != nil {
+ return err
+ }
+
+ opts.ReadPreference, err = readpref.New(mode, readPrefOpts...)
+ if err != nil {
+ return err
+ }
+ }
+
+ if connString.RetryWritesSet {
+ opts.RetryWrites = &connString.RetryWrites
+ }
+
+ if connString.RetryReadsSet {
+ opts.RetryReads = &connString.RetryReads
+ }
+
+ if connString.MaxAdaptiveRetriesSet {
+ opts.MaxAdaptiveRetries = &connString.MaxAdaptiveRetries
+ }
+
+ if connString.EnableOverloadRetargetingSet {
+ opts.EnableOverloadRetargeting = &connString.EnableOverloadRetargeting
+ }
+
+ if connString.ReplicaSet != "" {
+ opts.ReplicaSet = &connString.ReplicaSet
+ }
+
+ if connString.ServerSelectionTimeoutSet {
+ opts.ServerSelectionTimeout = &connString.ServerSelectionTimeout
+ }
+
+ if connString.SRVMaxHosts != 0 {
+ opts.SRVMaxHosts = &connString.SRVMaxHosts
+ }
+
+ if connString.SRVServiceName != "" {
+ opts.SRVServiceName = &connString.SRVServiceName
+ }
+
+ if connString.SSL {
+ tlsConfig := new(tls.Config)
+
+ if connString.SSLCaFileSet {
+ if err := addCACertFromFile(tlsConfig, connString.SSLCaFile); err != nil {
+ return err
+ }
+ }
+
+ if connString.SSLInsecure {
+ tlsConfig.InsecureSkipVerify = true
+ }
+
+ var x509Subject string
+ var keyPasswd string
+ if connString.SSLClientCertificateKeyPasswordSet && connString.SSLClientCertificateKeyPassword != nil {
+ keyPasswd = connString.SSLClientCertificateKeyPassword()
+ }
+
+ var err error
+ if connString.SSLClientCertificateKeyFileSet {
+ x509Subject, err = addClientCertFromConcatenatedFile(tlsConfig, connString.SSLClientCertificateKeyFile, keyPasswd)
+ } else if connString.SSLCertificateFileSet || connString.SSLPrivateKeyFileSet {
+ x509Subject, err = addClientCertFromSeparateFiles(tlsConfig, connString.SSLCertificateFile,
+ connString.SSLPrivateKeyFile, keyPasswd)
+ }
+
+ if err != nil {
+ return err
+ }
+
+ // If a username wasn't specified fork x509, add one from the certificate.
+ if opts.Auth != nil && strings.ToLower(opts.Auth.AuthMechanism) == "mongodb-x509" && opts.Auth.Username == "" {
+ // The Go x509 package gives the subject with the pairs in reverse order that we want.
+ opts.Auth.Username = extractX509UsernameFromSubject(x509Subject)
+ }
+
+ opts.TLSConfig = tlsConfig
+ }
+
+ if connString.JSet || connString.WString != "" || connString.WNumberSet {
+ opts.WriteConcern = &writeconcern.WriteConcern{}
+
+ if len(connString.WString) > 0 {
+ opts.WriteConcern.W = connString.WString
+ } else if connString.WNumberSet {
+ opts.WriteConcern.W = connString.WNumber
+ }
+
+ if connString.JSet {
+ opts.WriteConcern.Journal = &connString.J
+ }
+ }
+
+ if connString.ZlibLevelSet {
+ opts.ZlibLevel = &connString.ZlibLevel
+ }
+ if connString.ZstdLevelSet {
+ opts.ZstdLevel = &connString.ZstdLevel
+ }
+
+ if connString.SSLDisableOCSPEndpointCheckSet {
+ opts.DisableOCSPEndpointCheck = &connString.SSLDisableOCSPEndpointCheck
+ }
+
+ if connString.TimeoutSet {
+ opts.Timeout = &connString.Timeout
+ }
+
+ return nil
+}
+
+// GetURI returns the original URI used to configure the ClientOptions instance.
+// If ApplyURI was not called during construction, this returns "".
+func (c *ClientOptions) GetURI() string {
+ if c != nil && c.connString != nil {
+ return c.connString.Original
+ }
+
+ return ""
+}
+
+// Validate validates the client options. This method will return the first
+// error found.
+func (c *ClientOptions) Validate() error {
+ if c.err != nil {
+ return c.err
+ }
+
+ // Direct connections cannot be made if multiple hosts are specified or an SRV
+ // URI is used.
+ if c.Direct != nil && *c.Direct {
+ if len(c.Hosts) > 1 {
+ return errors.New("a direct connection cannot be made if multiple hosts are specified")
+ }
+ if c.connString != nil && c.connString.Scheme == connstring.SchemeMongoDBSRV {
+ return errors.New("a direct connection cannot be made if an SRV URI is used")
+ }
+ }
+
+ if c.HeartbeatInterval != nil && *c.HeartbeatInterval < (500*time.Millisecond) {
+ return fmt.Errorf("heartbeatFrequencyMS must exceed the minimum heartbeat interval of 500ms, got heartbeatFrequencyMS=%q",
+ *c.HeartbeatInterval)
+ }
+
+ if c.MaxPoolSize != nil && c.MinPoolSize != nil && *c.MaxPoolSize != 0 &&
+ *c.MinPoolSize > *c.MaxPoolSize {
+ return fmt.Errorf("minPoolSize must be less than or equal to maxPoolSize, got minPoolSize=%d maxPoolSize=%d",
+ *c.MinPoolSize, *c.MaxPoolSize)
+ }
+
+ // verify server API version if ServerAPIOptions are passed in.
+ if c.ServerAPIOptions != nil {
+ if err := c.ServerAPIOptions.ServerAPIVersion.Validate(); err != nil {
+ return err
+ }
+ }
+
+ // Validation for load-balanced mode.
+ if c.LoadBalanced != nil && *c.LoadBalanced {
+ if len(c.Hosts) > 1 {
+ return connstring.ErrLoadBalancedWithMultipleHosts
+ }
+ if c.ReplicaSet != nil {
+ return connstring.ErrLoadBalancedWithReplicaSet
+ }
+ if c.Direct != nil && *c.Direct {
+ return connstring.ErrLoadBalancedWithDirectConnection
+ }
+ }
+
+ // Validation for srvMaxHosts.
+ if c.SRVMaxHosts != nil && *c.SRVMaxHosts > 0 {
+ if c.ReplicaSet != nil {
+ return connstring.ErrSRVMaxHostsWithReplicaSet
+ }
+ if c.LoadBalanced != nil && *c.LoadBalanced {
+ return connstring.ErrSRVMaxHostsWithLoadBalanced
+ }
+ }
+
+ if mode := c.ServerMonitoringMode; mode != nil && !connstring.IsValidServerMonitoringMode(*mode) {
+ return fmt.Errorf("invalid server monitoring mode: %q", *mode)
+ }
+
+ if to := c.Timeout; to != nil && *to < 0 {
+ return fmt.Errorf(`invalid value %q for "Timeout": value must be positive`, *to)
+ }
+
+ // OIDC Validation
+ if c.Auth != nil && c.Auth.AuthMechanism == auth.MongoDBOIDC {
+ if c.Auth.Password != "" {
+ return fmt.Errorf("password must not be set for the %s auth mechanism", auth.MongoDBOIDC)
+ }
+ if c.Auth.OIDCMachineCallback != nil && c.Auth.OIDCHumanCallback != nil {
+ return fmt.Errorf("cannot set both OIDCMachineCallback and OIDCHumanCallback, only one may be specified")
+ }
+ if c.Auth.OIDCHumanCallback == nil && c.Auth.AuthMechanismProperties[auth.AllowedHostsProp] != "" {
+ return fmt.Errorf("cannot specify ALLOWED_HOSTS without an OIDCHumanCallback")
+ }
+ if c.Auth.OIDCMachineCallback == nil && c.Auth.OIDCHumanCallback == nil && c.Auth.AuthMechanismProperties[auth.EnvironmentProp] == "" {
+ return errors.New("must specify at least one of OIDCMachineCallback, OIDCHumanCallback, or ENVIRONMENT authMechanismProperty")
+ }
+
+ // Return an error if an unsupported authMechanismProperty is specified
+ // for MONGODB-OIDC.
+ for prop := range c.Auth.AuthMechanismProperties {
+ switch prop {
+ case auth.AllowedHostsProp, auth.EnvironmentProp, auth.ResourceProp:
+ default:
+ return fmt.Errorf("auth mechanism property %q is not valid for MONGODB-OIDC", prop)
+ }
+ }
+
+ if env, ok := c.Auth.AuthMechanismProperties[auth.EnvironmentProp]; ok {
+ switch env {
+ case auth.GCPEnvironmentValue, auth.AzureEnvironmentValue:
+ if c.Auth.AuthMechanismProperties[auth.ResourceProp] == "" {
+ return fmt.Errorf("%q must be set for the %s %q", auth.ResourceProp, env, auth.EnvironmentProp)
+ }
+ fallthrough
+ case auth.K8SEnvironmentValue:
+ if c.Auth.OIDCMachineCallback != nil {
+ return fmt.Errorf("OIDCMachineCallback cannot be specified with the %s %q", env, auth.EnvironmentProp)
+ }
+ if c.Auth.OIDCHumanCallback != nil {
+ return fmt.Errorf("OIDCHumanCallback cannot be specified with the %s %q", env, auth.EnvironmentProp)
+ }
+ case auth.TestEnvironmentValue:
+ if c.Auth.AuthMechanismProperties[auth.ResourceProp] != "" {
+ return fmt.Errorf("%q must not be set for the %s %q", auth.ResourceProp, env, auth.EnvironmentProp)
+ }
+ if c.Auth.Username != "" {
+ return fmt.Errorf("must not specify username for %s %q", env, auth.EnvironmentProp)
+ }
+ default:
+ return fmt.Errorf("the %s %q is not supported for MONGODB-OIDC", env, auth.EnvironmentProp)
+ }
+ }
+ }
+
+ return nil
+}
+
+// ApplyURI parses the given URI and sets options accordingly. The URI can contain host names, IPv4/IPv6 literals, or
+// an SRV record that will be resolved when the Client is created. When using an SRV record, TLS support is
+// implicitly enabled. Specify the "tls=false" URI option to override this.
+//
+// If the connection string contains any options that have previously been set, it will overwrite them. Options that
+// correspond to multiple URI parameters, such as WriteConcern, will be completely overwritten if any of the query
+// parameters are specified. If an option is set on ClientOptions after this method is called, that option will override
+// any option applied via the connection string.
+//
+// If the URI format is incorrect or there are conflicting options specified in the URI an error will be recorded and
+// can be retrieved by calling Validate.
+//
+// For more information about the URI format, see https://www.mongodb.com/docs/manual/reference/connection-string/. See
+// mongo.Connect documentation for examples of using URIs for different Client configurations.
+func (c *ClientOptions) ApplyURI(uri string) *ClientOptions {
+ if c.err != nil {
+ return c
+ }
+
+ c.err = setURIOpts(uri, c)
+
+ return c
+}
+
+// SetAppName specifies an application name that is sent to the server when creating new connections. It is used by the
+// server to log connection and profiling information (e.g. slow query logs). This can also be set through the "appName"
+// URI option (e.g "appName=example_application"). The default is empty, meaning no app name will be sent.
+func (c *ClientOptions) SetAppName(s string) *ClientOptions {
+ c.AppName = &s
+
+ return c
+}
+
+// SetAuth specifies a Credential containing options for configuring authentication. See the options.Credential
+// documentation for more information about Credential fields. The default is an empty Credential, meaning no
+// authentication will be configured.
+func (c *ClientOptions) SetAuth(auth Credential) *ClientOptions {
+ c.Auth = &auth
+
+ return c
+}
+
+// SetCompressors sets the compressors that can be used when communicating with a server. Valid values are:
+//
+// 1. "snappy"
+//
+// 2. "zlib"
+//
+// 3. "zstd" - requires server version >= 4.2, and driver version >= 1.2.0 with cgo support enabled or driver
+// version >= 1.3.0 without cgo.
+//
+// If this option is specified, the driver will perform a negotiation with the server to determine a common list of
+// compressors and will use the first one in that list when performing operations. See
+// https://www.mongodb.com/docs/manual/reference/program/mongod/#cmdoption-mongod-networkmessagecompressors for more
+// information about configuring compression on the server and the server-side defaults.
+//
+// This can also be set through the "compressors" URI option (e.g. "compressors=zstd,zlib,snappy"). The default is
+// an empty slice, meaning no compression will be enabled.
+func (c *ClientOptions) SetCompressors(comps []string) *ClientOptions {
+ c.Compressors = comps
+
+ return c
+}
+
+// SetConnectTimeout specifies a timeout that is used for creating connections to the server. This can be set through
+// ApplyURI with the "connectTimeoutMS" (e.g "connectTimeoutMS=30") option. If set to 0, no timeout will be used. The
+// default is 30 seconds.
+func (c *ClientOptions) SetConnectTimeout(d time.Duration) *ClientOptions {
+ c.ConnectTimeout = &d
+
+ return c
+}
+
+// SetDialer specifies a custom ContextDialer to be used to create new connections to the server. This method overrides
+// the default net.Dialer, so dialer options such as Timeout, KeepAlive, Resolver, etc can be set.
+// See https://golang.org/pkg/net/#Dialer for more information about the net.Dialer type.
+func (c *ClientOptions) SetDialer(d ContextDialer) *ClientOptions {
+ c.Dialer = d
+
+ return c
+}
+
+// SetDirect specifies whether or not a direct connect should be made. If set to true, the driver will only connect to
+// the host provided in the URI and will not discover other hosts in the cluster. This can also be set through the
+// "directConnection" URI option. This option cannot be set to true if multiple hosts are specified, either through
+// ApplyURI or SetHosts, or an SRV URI is used.
+//
+// As of driver version 1.4, the "connect" URI option has been deprecated and replaced with "directConnection". The
+// "connect" URI option has two values:
+//
+// 1. "connect=direct" for direct connections. This corresponds to "directConnection=true".
+//
+// 2. "connect=automatic" for automatic discovery. This corresponds to "directConnection=false"
+//
+// If the "connect" and "directConnection" URI options are both specified in the connection string, their values must
+// not conflict. Direct connections are not valid if multiple hosts are specified or an SRV URI is used. The default
+// value for this option is false.
+func (c *ClientOptions) SetDirect(b bool) *ClientOptions {
+ c.Direct = &b
+
+ return c
+}
+
+// SetHeartbeatInterval specifies the amount of time to wait between periodic background server checks. This can also be
+// set through the "heartbeatFrequencyMS" URI option (e.g. "heartbeatFrequencyMS=10000"). The default is 10 seconds.
+// The minimum is 500ms.
+func (c *ClientOptions) SetHeartbeatInterval(d time.Duration) *ClientOptions {
+ c.HeartbeatInterval = &d
+
+ return c
+}
+
+// SetHosts specifies a list of host names or IP addresses for servers in a cluster. Both IPv4 and IPv6 addresses are
+// supported. IPv6 literals must be enclosed in '[]' following RFC-2732 syntax.
+//
+// Hosts can also be specified as a comma-separated list in a URI. For example, to include "localhost:27017" and
+// "localhost:27018", a URI could be "mongodb://localhost:27017,localhost:27018". The default is ["localhost:27017"]
+func (c *ClientOptions) SetHosts(s []string) *ClientOptions {
+ c.Hosts = s
+
+ return c
+}
+
+// SetLoadBalanced specifies whether or not the MongoDB deployment is hosted behind a load balancer. This can also be
+// set through the "loadBalanced" URI option. The driver will error during Client configuration if this option is set
+// to true and one of the following conditions are met:
+//
+// 1. Multiple hosts are specified, either via the ApplyURI or SetHosts methods. This includes the case where an SRV
+// URI is used and the SRV record resolves to multiple hostnames.
+// 2. A replica set name is specified, either via the URI or the SetReplicaSet method.
+// 3. The options specify whether or not a direct connection should be made, either via the URI or the SetDirect method.
+//
+// The default value is false.
+func (c *ClientOptions) SetLoadBalanced(lb bool) *ClientOptions {
+ c.LoadBalanced = &lb
+
+ return c
+}
+
+// SetLocalThreshold specifies the width of the 'latency window': when choosing between multiple suitable servers for an
+// operation, this is the acceptable non-negative delta between shortest and longest average round-trip times. A server
+// within the latency window is selected randomly. This can also be set through the "localThresholdMS" URI option (e.g.
+// "localThresholdMS=15000"). The default is 15 milliseconds.
+func (c *ClientOptions) SetLocalThreshold(d time.Duration) *ClientOptions {
+ c.LocalThreshold = &d
+
+ return c
+}
+
+// SetLoggerOptions specifies a LoggerOptions containing options for
+// configuring a logger.
+func (c *ClientOptions) SetLoggerOptions(lopts *LoggerOptions) *ClientOptions {
+ c.LoggerOptions = lopts
+
+ return c
+}
+
+// SetMaxConnIdleTime specifies the maximum amount of time that a connection will remain idle in a connection pool
+// before it is removed from the pool and closed. This can also be set through the "maxIdleTimeMS" URI option (e.g.
+// "maxIdleTimeMS=10000"). The default is 0, meaning a connection can remain unused indefinitely.
+func (c *ClientOptions) SetMaxConnIdleTime(d time.Duration) *ClientOptions {
+ c.MaxConnIdleTime = &d
+
+ return c
+}
+
+// SetMaxPoolSize specifies that maximum number of connections allowed in the driver's connection pool to each server.
+// Requests to a server will block if this maximum is reached. This can also be set through the "maxPoolSize" URI option
+// (e.g. "maxPoolSize=100"). If this is 0, maximum connection pool size is not limited. The default is 100.
+func (c *ClientOptions) SetMaxPoolSize(u uint64) *ClientOptions {
+ c.MaxPoolSize = &u
+
+ return c
+}
+
+// SetMinPoolSize specifies the minimum number of connections allowed in the driver's connection pool to each server. If
+// this is non-zero, each server's pool will be maintained in the background to ensure that the size does not fall below
+// the minimum. This can also be set through the "minPoolSize" URI option (e.g. "minPoolSize=100"). The default is 0.
+func (c *ClientOptions) SetMinPoolSize(u uint64) *ClientOptions {
+ c.MinPoolSize = &u
+
+ return c
+}
+
+// SetMaxConnecting specifies the maximum number of connections a connection pool may establish simultaneously. This can
+// also be set through the "maxConnecting" URI option (e.g. "maxConnecting=2"). If this is 0, the default is used. The
+// default is 2. Values greater than 100 are not recommended.
+func (c *ClientOptions) SetMaxConnecting(u uint64) *ClientOptions {
+ c.MaxConnecting = &u
+
+ return c
+}
+
+// SetPoolMonitor specifies a PoolMonitor to receive connection pool events. See the event.PoolMonitor documentation
+// for more information about the structure of the monitor and events that can be received.
+func (c *ClientOptions) SetPoolMonitor(m *event.PoolMonitor) *ClientOptions {
+ c.PoolMonitor = m
+
+ return c
+}
+
+// SetMonitor specifies a CommandMonitor to receive command events. See the event.CommandMonitor documentation for more
+// information about the structure of the monitor and events that can be received.
+func (c *ClientOptions) SetMonitor(m *event.CommandMonitor) *ClientOptions {
+ c.Monitor = m
+
+ return c
+}
+
+// SetServerMonitor specifies an SDAM monitor used to monitor SDAM events.
+func (c *ClientOptions) SetServerMonitor(m *event.ServerMonitor) *ClientOptions {
+ c.ServerMonitor = m
+
+ return c
+}
+
+// SetReadConcern specifies the read concern to use for read operations. A read concern level can also be set through
+// the "readConcernLevel" URI option (e.g. "readConcernLevel=majority"). The default is nil, meaning the server will use
+// its configured default.
+func (c *ClientOptions) SetReadConcern(rc *readconcern.ReadConcern) *ClientOptions {
+ c.ReadConcern = rc
+
+ return c
+}
+
+// SetReadPreference specifies the read preference to use for read operations. This can also be set through the
+// following URI options:
+//
+// 1. "readPreference" - Specify the read preference mode (e.g. "readPreference=primary").
+//
+// 2. "readPreferenceTags": Specify one or more read preference tags
+// (e.g. "readPreferenceTags=region:south,datacenter:A").
+//
+// 3. "maxStalenessSeconds" (or "maxStaleness"): Specify a maximum replication lag for reads from secondaries in a
+// replica set (e.g. "maxStalenessSeconds=10").
+//
+// The default is readpref.Primary(). See https://www.mongodb.com/docs/manual/core/read-preference/#read-preference for
+// more information about read preferences.
+func (c *ClientOptions) SetReadPreference(rp *readpref.ReadPref) *ClientOptions {
+ c.ReadPreference = rp
+
+ return c
+}
+
+// SetBSONOptions configures optional BSON marshaling and unmarshaling behavior.
+func (c *ClientOptions) SetBSONOptions(bopts *BSONOptions) *ClientOptions {
+ c.BSONOptions = bopts
+
+ return c
+}
+
+// SetRegistry specifies the BSON registry to use for BSON marshalling/unmarshalling operations. The default is
+// bson.NewRegistry().
+func (c *ClientOptions) SetRegistry(registry *bson.Registry) *ClientOptions {
+ c.Registry = registry
+
+ return c
+}
+
+// SetReplicaSet specifies the replica set name for the cluster. If specified, the cluster will be treated as a replica
+// set and the driver will automatically discover all servers in the set, starting with the nodes specified through
+// ApplyURI or SetHosts. All nodes in the replica set must have the same replica set name, or they will not be
+// considered as part of the set by the Client. This can also be set through the "replicaSet" URI option (e.g.
+// "replicaSet=replset"). The default is empty.
+func (c *ClientOptions) SetReplicaSet(s string) *ClientOptions {
+ c.ReplicaSet = &s
+
+ return c
+}
+
+// SetRetryWrites specifies whether supported write operations should be retried once on certain errors, such as network
+// errors.
+//
+// Supported operations are InsertOne, UpdateOne, ReplaceOne, DeleteOne, FindOneAndDelete, FindOneAndReplace,
+// FindOneAndDelete, InsertMany, and BulkWrite. Note that BulkWrite requests must not include UpdateManyModel or
+// DeleteManyModel instances to be considered retryable. Unacknowledged writes will not be retried, even if this option
+// is set to true.
+//
+// This option only works on a replica set or sharded cluster and will be ignored for any other cluster type.
+// This can also be set through the "retryWrites" URI option (e.g. "retryWrites=true"). The default is true.
+func (c *ClientOptions) SetRetryWrites(b bool) *ClientOptions {
+ c.RetryWrites = &b
+
+ return c
+}
+
+// SetRetryReads specifies whether supported read operations should be retried once on certain errors, such as network
+// errors.
+//
+// Supported operations are Find, FindOne, Aggregate without a $out stage, Distinct, CountDocuments,
+// EstimatedDocumentCount, Watch (for Client, Database, and Collection), ListCollections, and ListDatabases. Note that
+// operations run through RunCommand are not retried.
+//
+// The default is true.
+func (c *ClientOptions) SetRetryReads(b bool) *ClientOptions {
+ c.RetryReads = &b
+
+ return c
+}
+
+// SetMaxAdaptiveRetries specifies the maximum number of times the driver should retry operations that fail with a
+// server side overload error. MaxAdaptiveRetries can also be set through the "maxAdaptiveRetries" URI option
+// (e.g. "maxAdaptiveRetries=5").
+func (c *ClientOptions) SetMaxAdaptiveRetries(n uint) *ClientOptions {
+ c.MaxAdaptiveRetries = &n
+
+ return c
+}
+
+// SetEnableOverloadRetargeting specifies whether the driver should enable overload retargeting for operations that fail
+// with a server side overload error. EnableOverloadRetargeting can also be set through the "enableOverloadRetargeting"
+// URI option (e.g. "enableOverloadRetargeting=true").
+func (c *ClientOptions) SetEnableOverloadRetargeting(b bool) *ClientOptions {
+ c.EnableOverloadRetargeting = &b
+
+ return c
+}
+
+// SetServerSelectionTimeout specifies how long the driver will wait to find an available, suitable server to execute an
+// operation. This can also be set through the "serverSelectionTimeoutMS" URI option (e.g.
+// "serverSelectionTimeoutMS=30000"). The default value is 30 seconds.
+func (c *ClientOptions) SetServerSelectionTimeout(d time.Duration) *ClientOptions {
+ c.ServerSelectionTimeout = &d
+
+ return c
+}
+
+// SetTimeout specifies the amount of time that a single operation run on this
+// Client can execute before returning an error. The deadline of any operation
+// run through the Client will be honored above any Timeout set on the Client;
+// Timeout will only be honored if there is no deadline on the operation
+// Context. Timeout can also be set through the "timeoutMS" URI option
+// (e.g. "timeoutMS=1000"). The default value is nil, meaning operations do not
+// inherit a timeout from the Client.
+//
+// If any Timeout is set (even 0) on the Client, the values of MaxTime on
+// operation options, TransactionOptions.MaxCommitTime and
+// SessionOptions.DefaultMaxCommitTime will be ignored.
+func (c *ClientOptions) SetTimeout(d time.Duration) *ClientOptions {
+ c.Timeout = &d
+
+ return c
+}
+
+// SetTLSConfig specifies a tls.Config instance to use use to configure TLS on all connections created to the cluster.
+// This can also be set through the following URI options:
+//
+// 1. "tls" (or "ssl"): Specify if TLS should be used (e.g. "tls=true").
+//
+// 2. Either "tlsCertificateKeyFile" (or "sslClientCertificateKeyFile") or a combination of "tlsCertificateFile" and
+// "tlsPrivateKeyFile". The "tlsCertificateKeyFile" option specifies a path to the client certificate and private key,
+// which must be concatenated into one file. The "tlsCertificateFile" and "tlsPrivateKey" combination specifies separate
+// paths to the client certificate and private key, respectively. Note that if "tlsCertificateKeyFile" is used, the
+// other two options must not be specified. Only the subject name of the first certificate is honored as the username
+// for X509 auth in a file with multiple certs.
+//
+// 3. "tlsCertificateKeyFilePassword" (or "sslClientCertificateKeyPassword"): Specify the password to decrypt the client
+// private key file (e.g. "tlsCertificateKeyFilePassword=password").
+//
+// 4. "tlsCaFile" (or "sslCertificateAuthorityFile"): Specify the path to a single or bundle of certificate authorities
+// to be considered trusted when making a TLS connection (e.g. "tlsCaFile=/path/to/caFile").
+//
+// 5. "tlsInsecure" (or "sslInsecure"): Specifies whether or not certificates and hostnames received from the server
+// should be validated. If true (e.g. "tlsInsecure=true"), the TLS library will accept any certificate presented by the
+// server and any host name in that certificate. Note that setting this to true makes TLS susceptible to
+// man-in-the-middle attacks and should only be done for testing.
+//
+// The default is nil, meaning no TLS will be enabled.
+func (c *ClientOptions) SetTLSConfig(cfg *tls.Config) *ClientOptions {
+ c.TLSConfig = cfg
+
+ return c
+}
+
+// SetHTTPClient specifies the http.Client to be used for any HTTP requests.
+//
+// This should only be used to set custom HTTP client configurations. By default, the connection will use an httputil.DefaultHTTPClient.
+func (c *ClientOptions) SetHTTPClient(client *http.Client) *ClientOptions {
+ c.HTTPClient = client
+
+ return c
+}
+
+// SetWriteConcern specifies the write concern to use to for write operations. This can also be set through the following
+// URI options:
+//
+// 1. "w": Specify the number of nodes in the cluster that must acknowledge write operations before the operation
+// returns or "majority" to specify that a majority of the nodes must acknowledge writes. This can either be an integer
+// (e.g. "w=10") or the string "majority" (e.g. "w=majority").
+//
+// 2. "wTimeoutMS": Specify how long write operations should wait for the correct number of nodes to acknowledge the
+// operation (e.g. "wTimeoutMS=1000").
+//
+// 3. "journal": Specifies whether or not write operations should be written to an on-disk journal on the server before
+// returning (e.g. "journal=true").
+//
+// The default is nil, meaning the server will use its configured default.
+func (c *ClientOptions) SetWriteConcern(wc *writeconcern.WriteConcern) *ClientOptions {
+ c.WriteConcern = wc
+
+ return c
+}
+
+// SetZlibLevel specifies the level for the zlib compressor. This option is ignored if zlib is not specified as a
+// compressor through ApplyURI or SetCompressors. Supported values are -1 through 9, inclusive. -1 tells the zlib
+// library to use its default, 0 means no compression, 1 means best speed, and 9 means best compression.
+// This can also be set through the "zlibCompressionLevel" URI option (e.g. "zlibCompressionLevel=-1"). Defaults to -1.
+func (c *ClientOptions) SetZlibLevel(level int) *ClientOptions {
+ c.ZlibLevel = &level
+
+ return c
+}
+
+// SetZstdLevel sets the level for the zstd compressor. This option is ignored if zstd is not specified as a compressor
+// through ApplyURI or SetCompressors. Supported values are 1 through 20, inclusive. 1 means best speed and 20 means
+// best compression. This can also be set through the "zstdCompressionLevel" URI option. Defaults to 6.
+func (c *ClientOptions) SetZstdLevel(level int) *ClientOptions {
+ c.ZstdLevel = &level
+
+ return c
+}
+
+// SetAutoEncryptionOptions specifies an AutoEncryptionOptions instance to automatically encrypt and decrypt commands
+// and their results. See the options.AutoEncryptionOptions documentation for more information about the supported
+// options.
+func (c *ClientOptions) SetAutoEncryptionOptions(aeopts *AutoEncryptionOptions) *ClientOptions {
+ c.AutoEncryptionOptions = aeopts
+
+ return c
+}
+
+// SetDisableOCSPEndpointCheck specifies whether or not the driver should reach out to OCSP responders to verify the
+// certificate status for certificates presented by the server that contain a list of OCSP responders.
+//
+// If set to true, the driver will verify the status of the certificate using a response stapled by the server, if there
+// is one, but will not send an HTTP request to any responders if there is no staple. In this case, the driver will
+// continue the connection even though the certificate status is not known.
+//
+// This can also be set through the tlsDisableOCSPEndpointCheck URI option. Both this URI option and tlsInsecure must
+// not be set at the same time and will error if they are. The default value is false.
+func (c *ClientOptions) SetDisableOCSPEndpointCheck(disableCheck bool) *ClientOptions {
+ c.DisableOCSPEndpointCheck = &disableCheck
+
+ return c
+}
+
+// SetServerAPIOptions specifies a ServerAPIOptions instance used to configure the API version sent to the server
+// when running commands. See the options.ServerAPIOptions documentation for more information about the supported
+// options.
+func (c *ClientOptions) SetServerAPIOptions(sopts *ServerAPIOptions) *ClientOptions {
+ c.ServerAPIOptions = sopts
+
+ return c
+}
+
+// SetServerMonitoringMode specifies the server monitoring protocol to use. See
+// the helper constants ServerMonitoringModeAuto, ServerMonitoringModePoll, and
+// ServerMonitoringModeStream for more information about valid server
+// monitoring modes.
+func (c *ClientOptions) SetServerMonitoringMode(mode string) *ClientOptions {
+ c.ServerMonitoringMode = &mode
+
+ return c
+}
+
+// SetSRVMaxHosts specifies the maximum number of SRV results to randomly select during polling. To limit the number
+// of hosts selected in SRV discovery, this function must be called before ApplyURI. This can also be set through
+// the "srvMaxHosts" URI option.
+func (c *ClientOptions) SetSRVMaxHosts(srvMaxHosts int) *ClientOptions {
+ c.SRVMaxHosts = &srvMaxHosts
+
+ return c
+}
+
+// SetSRVServiceName specifies a custom SRV service name to use in SRV polling. To use a custom SRV service name
+// in SRV discovery, this function must be called before ApplyURI. This can also be set through the "srvServiceName"
+// URI option.
+func (c *ClientOptions) SetSRVServiceName(srvName string) *ClientOptions {
+ c.SRVServiceName = &srvName
+
+ return c
+}
+
+// SetDriverInfo configures optional data to include in the handshake's client
+// metadata, delimited by "|" with the driver-generated data. This should be
+// used by libraries wrapping the driver, e.g. ODMs.
+func (c *ClientOptions) SetDriverInfo(info *DriverInfo) *ClientOptions {
+ c.DriverInfo = info
+
+ return c
+}
+
+// addCACertFromFile adds a root CA certificate to the configuration given a path
+// to the containing file.
+func addCACertFromFile(cfg *tls.Config, file string) error {
+ data, err := ioutil.ReadFile(file)
+ if err != nil {
+ return err
+ }
+
+ if cfg.RootCAs == nil {
+ cfg.RootCAs = x509.NewCertPool()
+ }
+ if !cfg.RootCAs.AppendCertsFromPEM(data) {
+ return errors.New("the specified CA file does not contain any valid certificates")
+ }
+
+ return nil
+}
+
+func addClientCertFromSeparateFiles(cfg *tls.Config, keyFile, certFile, keyPassword string) (string, error) {
+ keyData, err := ioutil.ReadFile(keyFile)
+ if err != nil {
+ return "", err
+ }
+ certData, err := ioutil.ReadFile(certFile)
+ if err != nil {
+ return "", err
+ }
+
+ keySize := len(keyData)
+ if keySize > 64*1024*1024 {
+ return "", errors.New("X.509 key must be less than 64 MiB")
+ }
+ certSize := len(certData)
+ if certSize > 64*1024*1024 {
+ return "", errors.New("X.509 certificate must be less than 64 MiB")
+ }
+ dataSize := int64(keySize) + int64(certSize) + 1
+ if dataSize > math.MaxInt {
+ return "", errors.New("size overflow")
+ }
+ data := make([]byte, 0, int(dataSize))
+ data = append(data, keyData...)
+ data = append(data, '\n')
+ data = append(data, certData...)
+ return addClientCertFromBytes(cfg, data, keyPassword)
+}
+
+func addClientCertFromConcatenatedFile(cfg *tls.Config, certKeyFile, keyPassword string) (string, error) {
+ data, err := ioutil.ReadFile(certKeyFile)
+ if err != nil {
+ return "", err
+ }
+
+ return addClientCertFromBytes(cfg, data, keyPassword)
+}
+
+// addClientCertFromBytes adds client certificates to the configuration given a path to the
+// containing file and returns the subject name in the first certificate.
+func addClientCertFromBytes(cfg *tls.Config, data []byte, keyPasswd string) (string, error) {
+ var currentBlock *pem.Block
+ var certDecodedBlock []byte
+ var certBlocks, keyBlocks [][]byte
+
+ remaining := data
+ start := 0
+ for {
+ currentBlock, remaining = pem.Decode(remaining)
+ if currentBlock == nil {
+ break
+ }
+
+ if currentBlock.Type == "CERTIFICATE" {
+ certBlock := data[start : len(data)-len(remaining)]
+ certBlocks = append(certBlocks, certBlock)
+ // Assign the certDecodedBlock when it is never set,
+ // so only the first certificate is honored in a file with multiple certs.
+ if certDecodedBlock == nil {
+ certDecodedBlock = currentBlock.Bytes
+ }
+ start += len(certBlock)
+ } else if strings.HasSuffix(currentBlock.Type, "PRIVATE KEY") {
+ isEncrypted := x509.IsEncryptedPEMBlock(currentBlock) || strings.Contains(currentBlock.Type, "ENCRYPTED PRIVATE KEY")
+ if isEncrypted {
+ if keyPasswd == "" {
+ return "", fmt.Errorf("no password provided to decrypt private key")
+ }
+
+ var keyBytes []byte
+ var err error
+ // Process the X.509-encrypted or PKCS-encrypted PEM block.
+ if x509.IsEncryptedPEMBlock(currentBlock) {
+ // Only covers encrypted PEM data with a DEK-Info header.
+ keyBytes, err = x509.DecryptPEMBlock(currentBlock, []byte(keyPasswd))
+ if err != nil {
+ return "", err
+ }
+ } else if strings.Contains(currentBlock.Type, "ENCRYPTED") {
+ // The pkcs8 package only handles the PKCS #5 v2.0 scheme.
+ decrypted, err := pkcs8.ParsePKCS8PrivateKey(currentBlock.Bytes, []byte(keyPasswd))
+ if err != nil {
+ return "", err
+ }
+ keyBytes, err = x509.MarshalPKCS8PrivateKey(decrypted)
+ if err != nil {
+ return "", err
+ }
+ }
+ var encoded bytes.Buffer
+ err = pem.Encode(&encoded, &pem.Block{Type: currentBlock.Type, Bytes: keyBytes})
+ if err != nil {
+ return "", fmt.Errorf("error encoding private key as PEM: %w", err)
+ }
+ keyBlock := encoded.Bytes()
+ keyBlocks = append(keyBlocks, keyBlock)
+ start = len(data) - len(remaining)
+ } else {
+ keyBlock := data[start : len(data)-len(remaining)]
+ keyBlocks = append(keyBlocks, keyBlock)
+ start += len(keyBlock)
+ }
+ }
+ }
+ if len(certBlocks) == 0 {
+ return "", fmt.Errorf("failed to find CERTIFICATE")
+ }
+ if len(keyBlocks) == 0 {
+ return "", fmt.Errorf("failed to find PRIVATE KEY")
+ }
+
+ cert, err := tls.X509KeyPair(bytes.Join(certBlocks, []byte("\n")), bytes.Join(keyBlocks, []byte("\n")))
+ if err != nil {
+ return "", err
+ }
+
+ cfg.Certificates = append(cfg.Certificates, cert)
+
+ // The documentation for the tls.X509KeyPair indicates that the Leaf certificate is not
+ // retained.
+ crt, err := x509.ParseCertificate(certDecodedBlock)
+ if err != nil {
+ return "", err
+ }
+
+ return crt.Subject.String(), nil
+}
+
+func stringSliceContains(source []string, target string) bool {
+ for _, str := range source {
+ if str == target {
+ return true
+ }
+ }
+ return false
+}
+
+// create a username for x509 authentication from an x509 certificate subject.
+func extractX509UsernameFromSubject(subject string) string {
+ // the Go x509 package gives the subject with the pairs in the reverse order from what we want.
+ pairs := strings.Split(subject, ",")
+ for left, right := 0, len(pairs)-1; left < right; left, right = left+1, right-1 {
+ pairs[left], pairs[right] = pairs[right], pairs[left]
+ }
+
+ return strings.Join(pairs, ",")
+}
+
+// MergeClientOptions combines the given *ClientOptions into a single
+// *ClientOptions in a last one wins fashion. The specified options are merged
+// with the existing options on the client, with the specified options taking
+// precedence.
+func MergeClientOptions(opts ...*ClientOptions) *ClientOptions {
+ if len(opts) == 1 {
+ if opts[0] == nil {
+ return Client()
+ }
+
+ return opts[0]
+ }
+
+ c := Client()
+ for _, opt := range opts {
+ if opt == nil {
+ continue
+ }
+ optValue := reflect.ValueOf(opt).Elem()
+ cValue := reflect.ValueOf(c).Elem()
+ for i := 0; i < optValue.NumField(); i++ {
+ field := optValue.Field(i)
+ fieldType := optValue.Type().Field(i)
+ // Check if the field is exported and can be set
+ if field.CanSet() && fieldType.PkgPath == "" && !field.IsZero() {
+ cValue.Field(i).Set(field)
+ }
+ }
+
+ // Manually handle unexported fields
+ if opt.err != nil {
+ c.err = opt.err
+ }
+
+ if opt.connString != nil {
+ c.connString = opt.connString
+ }
+ }
+
+ return c
+}
diff --git a/vendor/go.mongodb.org/mongo-driver/v2/mongo/options/collectionoptions.go b/vendor/go.mongodb.org/mongo-driver/v2/mongo/options/collectionoptions.go
new file mode 100644
index 0000000..9cc6d13
--- /dev/null
+++ b/vendor/go.mongodb.org/mongo-driver/v2/mongo/options/collectionoptions.go
@@ -0,0 +1,105 @@
+// Copyright (C) MongoDB, Inc. 2017-present.
+//
+// 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
+
+package options
+
+import (
+ "go.mongodb.org/mongo-driver/v2/bson"
+ "go.mongodb.org/mongo-driver/v2/mongo/readconcern"
+ "go.mongodb.org/mongo-driver/v2/mongo/readpref"
+ "go.mongodb.org/mongo-driver/v2/mongo/writeconcern"
+)
+
+// CollectionOptions represents arguments that can be used to configure a Collection.
+//
+// See corresponding setter methods for documentation.
+type CollectionOptions struct {
+ ReadConcern *readconcern.ReadConcern
+ WriteConcern *writeconcern.WriteConcern
+ ReadPreference *readpref.ReadPref
+ BSONOptions *BSONOptions
+ Registry *bson.Registry
+}
+
+// CollectionOptionsBuilder contains options to configure a Collection instance.
+// Each option can be set through setter functions. See documentation for each
+// setter function for an explanation of the option.
+type CollectionOptionsBuilder struct {
+ Opts []func(*CollectionOptions) error
+}
+
+// Collection creates a new CollectionOptions instance.
+func Collection() *CollectionOptionsBuilder {
+ return &CollectionOptionsBuilder{}
+}
+
+// List returns a list of CollectionOptions setter functions.
+func (c *CollectionOptionsBuilder) List() []func(*CollectionOptions) error {
+ return c.Opts
+}
+
+// SetReadConcern sets the value for the ReadConcern field. ReadConcern is the read concern to use for
+// operations executed on the Collection. The default value is nil, which means that the read concern
+// of the Database used to configure the Collection will be used.
+func (c *CollectionOptionsBuilder) SetReadConcern(rc *readconcern.ReadConcern) *CollectionOptionsBuilder {
+ c.Opts = append(c.Opts, func(opts *CollectionOptions) error {
+ opts.ReadConcern = rc
+
+ return nil
+ })
+
+ return c
+}
+
+// SetWriteConcern sets the value for the WriteConcern field. WriteConcern is the write concern to
+// use for operations executed on the Collection. The default value is nil, which means that the write
+// concern of the Database used to configure the Collection will be used.
+func (c *CollectionOptionsBuilder) SetWriteConcern(wc *writeconcern.WriteConcern) *CollectionOptionsBuilder {
+ c.Opts = append(c.Opts, func(opts *CollectionOptions) error {
+ opts.WriteConcern = wc
+
+ return nil
+ })
+
+ return c
+}
+
+// SetReadPreference sets the value for the ReadPreference field. ReadPreference is the read preference
+// to use for operations executed on the Collection. The default value is nil, which means that the
+// read preference of the Database used to configure the Collection will be used.
+func (c *CollectionOptionsBuilder) SetReadPreference(rp *readpref.ReadPref) *CollectionOptionsBuilder {
+ c.Opts = append(c.Opts, func(opts *CollectionOptions) error {
+ opts.ReadPreference = rp
+
+ return nil
+ })
+
+ return c
+}
+
+// SetBSONOptions configures optional BSON marshaling and unmarshaling behavior. BSONOptions configures
+// optional BSON marshaling and unmarshaling behavior.
+func (c *CollectionOptionsBuilder) SetBSONOptions(bopts *BSONOptions) *CollectionOptionsBuilder {
+ c.Opts = append(c.Opts, func(opts *CollectionOptions) error {
+ opts.BSONOptions = bopts
+
+ return nil
+ })
+
+ return c
+}
+
+// SetRegistry sets the value for the Registry field. Registry is the BSON registry to marshal and
+// unmarshal documents for operations executed on the Collection. The default value is nil, which
+// means that the registry of the Database used to configure the Collection will be used.
+func (c *CollectionOptionsBuilder) SetRegistry(r *bson.Registry) *CollectionOptionsBuilder {
+ c.Opts = append(c.Opts, func(opts *CollectionOptions) error {
+ opts.Registry = r
+
+ return nil
+ })
+ return c
+}
diff --git a/vendor/go.mongodb.org/mongo-driver/v2/mongo/options/countoptions.go b/vendor/go.mongodb.org/mongo-driver/v2/mongo/options/countoptions.go
new file mode 100644
index 0000000..f414bde
--- /dev/null
+++ b/vendor/go.mongodb.org/mongo-driver/v2/mongo/options/countoptions.go
@@ -0,0 +1,105 @@
+// Copyright (C) MongoDB, Inc. 2017-present.
+//
+// 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
+
+package options
+
+import "go.mongodb.org/mongo-driver/v2/internal/optionsutil"
+
+// CountOptions represents arguments that can be used to configure a
+// CountDocuments operation.
+//
+// See corresponding setter methods for documentation.
+type CountOptions struct {
+ Collation *Collation
+ Comment any
+ Hint any
+ Limit *int64
+ Skip *int64
+
+ // Deprecated: This option is for internal use only and should not be set. It may be changed or removed in any
+ // release.
+ Internal optionsutil.Options
+}
+
+// CountOptionsBuilder contains options to configure count operations. Each
+// option can be set through setter functions. See documentation for each setter
+// function for an explanation of the option.
+type CountOptionsBuilder struct {
+ Opts []func(*CountOptions) error
+}
+
+// Count creates a new CountOptions instance.
+func Count() *CountOptionsBuilder {
+ return &CountOptionsBuilder{}
+}
+
+// List returns a list of CountOptions setter functions.
+func (co *CountOptionsBuilder) List() []func(*CountOptions) error {
+ return co.Opts
+}
+
+// SetCollation sets the value for the Collation field. Specifies a collation to
+// use for string comparisons during the operation. The default value is nil,
+// which means the default collation of the collection will be used.
+func (co *CountOptionsBuilder) SetCollation(c *Collation) *CountOptionsBuilder {
+ co.Opts = append(co.Opts, func(opts *CountOptions) error {
+ opts.Collation = c
+
+ return nil
+ })
+
+ return co
+}
+
+// SetComment sets the value for the Comment field. Specifies a string or document that will be included
+// in server logs, profiling logs, and currentOp queries to help trace the operation. The default is nil,
+// which means that no comment will be included in the logs.
+func (co *CountOptionsBuilder) SetComment(comment any) *CountOptionsBuilder {
+ co.Opts = append(co.Opts, func(opts *CountOptions) error {
+ opts.Comment = comment
+
+ return nil
+ })
+
+ return co
+}
+
+// SetHint sets the value for the Hint field. Specifies the index to use for the aggregation. This should
+// either be the index name as a string or the index specification as a document. The driver will return
+// an error if the hint parameter is a multi-key map. The default value is nil, which means that no hint
+// will be sent.
+func (co *CountOptionsBuilder) SetHint(h any) *CountOptionsBuilder {
+ co.Opts = append(co.Opts, func(opts *CountOptions) error {
+ opts.Hint = h
+
+ return nil
+ })
+
+ return co
+}
+
+// SetLimit sets the value for the Limit field. Specifies the maximum number of documents to count.
+func (co *CountOptionsBuilder) SetLimit(i int64) *CountOptionsBuilder {
+ co.Opts = append(co.Opts, func(opts *CountOptions) error {
+ opts.Limit = &i
+
+ return nil
+ })
+
+ return co
+}
+
+// SetSkip sets the value for the Skip field. Specifies the number of documents to skip before counting.
+// The default value is 0.
+func (co *CountOptionsBuilder) SetSkip(i int64) *CountOptionsBuilder {
+ co.Opts = append(co.Opts, func(opts *CountOptions) error {
+ opts.Skip = &i
+
+ return nil
+ })
+
+ return co
+}
diff --git a/vendor/go.mongodb.org/mongo-driver/v2/mongo/options/createcollectionoptions.go b/vendor/go.mongodb.org/mongo-driver/v2/mongo/options/createcollectionoptions.go
new file mode 100644
index 0000000..4bac0d0
--- /dev/null
+++ b/vendor/go.mongodb.org/mongo-driver/v2/mongo/options/createcollectionoptions.go
@@ -0,0 +1,413 @@
+// Copyright (C) MongoDB, Inc. 2017-present.
+//
+// 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
+
+package options
+
+import (
+ "time"
+)
+
+// DefaultIndexOptions represents the default arguments for a collection to
+// apply on new indexes. This type can be used when creating a new collection
+// through the CreateCollectionOptions.SetDefaultIndexOptions method.
+//
+// See corresponding setter methods for documentation.
+type DefaultIndexOptions struct {
+ StorageEngine any
+}
+
+// DefaultIndexOptionsBuilder contains options to configure default index
+// operations. Each option can be set through setter functions. See
+// documentation for each setter function for an explanation of the option.
+type DefaultIndexOptionsBuilder struct {
+ Opts []func(*DefaultIndexOptions) error
+}
+
+// DefaultIndex creates a new DefaultIndexOptions instance.
+func DefaultIndex() *DefaultIndexOptionsBuilder {
+ return &DefaultIndexOptionsBuilder{}
+}
+
+// List returns a list of DefaultIndexOptions setter functions.
+func (d *DefaultIndexOptionsBuilder) List() []func(*DefaultIndexOptions) error {
+ return d.Opts
+}
+
+// SetStorageEngine sets the value for the StorageEngine field. Specifies the storage engine to use for
+// the index. The value must be a document in the form {: }. The default
+// value is nil, which means that the default storage engine will be used.
+func (d *DefaultIndexOptionsBuilder) SetStorageEngine(storageEngine any) *DefaultIndexOptionsBuilder {
+ d.Opts = append(d.Opts, func(opts *DefaultIndexOptions) error {
+ opts.StorageEngine = storageEngine
+
+ return nil
+ })
+
+ return d
+}
+
+// TimeSeriesOptions specifies arguments on a time-series collection.
+//
+// See corresponding setter methods for documentation.
+type TimeSeriesOptions struct {
+ TimeField string
+ MetaField *string
+ Granularity *string
+ BucketMaxSpan *time.Duration
+ BucketRounding *time.Duration
+}
+
+// TimeSeriesOptionsBuilder contains options to configure timeseries operations.
+// Each option can be set through setter functions. See documentation for each
+// setter function for an explanation of the option.
+type TimeSeriesOptionsBuilder struct {
+ Opts []func(*TimeSeriesOptions) error
+}
+
+// TimeSeries creates a new TimeSeriesOptions instance.
+func TimeSeries() *TimeSeriesOptionsBuilder {
+ return &TimeSeriesOptionsBuilder{}
+}
+
+// List returns a list of TimeSeriesOptions setter functions.
+func (tso *TimeSeriesOptionsBuilder) List() []func(*TimeSeriesOptions) error {
+ return tso.Opts
+}
+
+// SetTimeField sets the value for the TimeField. TimeField is the top-level field to be used
+// for time. Inserted documents must have this field, and the field must be of the BSON UTC
+// datetime type (0x9).
+func (tso *TimeSeriesOptionsBuilder) SetTimeField(timeField string) *TimeSeriesOptionsBuilder {
+ tso.Opts = append(tso.Opts, func(opts *TimeSeriesOptions) error {
+ opts.TimeField = timeField
+
+ return nil
+ })
+
+ return tso
+}
+
+// SetMetaField sets the value for the MetaField. MetaField is the name of the top-level field
+// describing the series. This field is used to group related data and may be of any BSON type,
+// except for array. This name may not be the same as the TimeField or _id. This field is optional.
+func (tso *TimeSeriesOptionsBuilder) SetMetaField(metaField string) *TimeSeriesOptionsBuilder {
+ tso.Opts = append(tso.Opts, func(opts *TimeSeriesOptions) error {
+ opts.MetaField = &metaField
+
+ return nil
+ })
+
+ return tso
+}
+
+// SetGranularity sets the value for Granularity. Granularity is the granularity of time-series data.
+// Allowed granularity options are "seconds", "minutes" and "hours". This field is optional.
+func (tso *TimeSeriesOptionsBuilder) SetGranularity(granularity string) *TimeSeriesOptionsBuilder {
+ tso.Opts = append(tso.Opts, func(opts *TimeSeriesOptions) error {
+ opts.Granularity = &granularity
+
+ return nil
+ })
+
+ return tso
+}
+
+// SetBucketMaxSpan sets the value for BucketMaxSpan. BucketMaxSpan is the maximum range of time
+// values for a bucket. The time.Duration is rounded down to the nearest second and applied as
+// the command option: "bucketRoundingSeconds". This field is optional.
+func (tso *TimeSeriesOptionsBuilder) SetBucketMaxSpan(dur time.Duration) *TimeSeriesOptionsBuilder {
+ tso.Opts = append(tso.Opts, func(opts *TimeSeriesOptions) error {
+ opts.BucketMaxSpan = &dur
+
+ return nil
+ })
+
+ return tso
+}
+
+// SetBucketRounding sets the value for BucketRounding. BucketRounding is used to determine the
+// minimum time boundary when opening a new bucket by rounding the first timestamp down to the next
+// multiple of this value. The time.Duration is rounded down to the nearest second and applied as
+// the command option: "bucketRoundingSeconds". This field is optional.
+func (tso *TimeSeriesOptionsBuilder) SetBucketRounding(dur time.Duration) *TimeSeriesOptionsBuilder {
+ tso.Opts = append(tso.Opts, func(opts *TimeSeriesOptions) error {
+ opts.BucketRounding = &dur
+
+ return nil
+ })
+
+ return tso
+}
+
+// CreateCollectionOptions represents arguments that can be used to configure a
+// CreateCollection operation.
+//
+// See corresponding setter methods for documentation.
+type CreateCollectionOptions struct {
+ Capped *bool
+ Collation *Collation
+ ChangeStreamPreAndPostImages any
+ DefaultIndexOptions *DefaultIndexOptionsBuilder
+ MaxDocuments *int64
+ SizeInBytes *int64
+ StorageEngine any
+ ValidationAction *string
+ ValidationLevel *string
+ Validator any
+ ExpireAfterSeconds *int64
+ TimeSeriesOptions *TimeSeriesOptionsBuilder
+ EncryptedFields any
+ ClusteredIndex any
+}
+
+// CreateCollectionOptionsBuilder contains options to configure a new
+// collection. Each option can be set through setter functions. See
+// documentation for each setter function for an explanation of the option.
+type CreateCollectionOptionsBuilder struct {
+ Opts []func(*CreateCollectionOptions) error
+}
+
+// CreateCollection creates a new CreateCollectionOptions instance.
+func CreateCollection() *CreateCollectionOptionsBuilder {
+ return &CreateCollectionOptionsBuilder{}
+}
+
+// List returns a list of CreateCollectionOptions setter functions.
+func (c *CreateCollectionOptionsBuilder) List() []func(*CreateCollectionOptions) error {
+ return c.Opts
+}
+
+// SetCapped sets the value for the Capped field. Specifies if the collection is capped
+// (see https://www.mongodb.com/docs/manual/core/capped-collections/). If true, the SizeInBytes
+// option must also be specified. The default value is false.
+func (c *CreateCollectionOptionsBuilder) SetCapped(capped bool) *CreateCollectionOptionsBuilder {
+ c.Opts = append(c.Opts, func(opts *CreateCollectionOptions) error {
+ opts.Capped = &capped
+
+ return nil
+ })
+
+ return c
+}
+
+// SetCollation sets the value for the Collation field. Specifies the default
+// collation for the new collection. The default value is nil.
+func (c *CreateCollectionOptionsBuilder) SetCollation(collation *Collation) *CreateCollectionOptionsBuilder {
+ c.Opts = append(c.Opts, func(opts *CreateCollectionOptions) error {
+ opts.Collation = collation
+
+ return nil
+ })
+
+ return c
+}
+
+// SetChangeStreamPreAndPostImages sets the value for the ChangeStreamPreAndPostImages field.
+// Specifies how change streams opened against the collection can return pre- and post-images of
+// updated documents. The value must be a document in the form {