Skip to content

Commit 83a2271

Browse files
RUST-1604 Add custom bucketing fields to timeseries options (mongodb#907)
1 parent ecb5108 commit 83a2271

10 files changed

+341
-35
lines changed

src/db/options.rs

Lines changed: 28 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -98,11 +98,7 @@ pub struct CreateCollectionOptions {
9898
/// Used to automatically delete documents in time series collections. See the [`create`
9999
/// command documentation](https://www.mongodb.com/docs/manual/reference/command/create/) for more
100100
/// information.
101-
#[serde(
102-
default,
103-
deserialize_with = "serde_util::deserialize_duration_option_from_u64_seconds",
104-
serialize_with = "serde_util::serialize_duration_option_as_int_secs"
105-
)]
101+
#[serde(default, with = "serde_util::duration_option_as_int_seconds")]
106102
pub expire_after_seconds: Option<Duration>,
107103

108104
/// Options for supporting change stream pre- and post-images.
@@ -192,8 +188,10 @@ pub struct IndexOptionDefaults {
192188
}
193189

194190
/// Specifies options for creating a timeseries collection.
191+
#[skip_serializing_none]
195192
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, TypedBuilder)]
196193
#[serde(rename_all = "camelCase")]
194+
#[builder(field_defaults(default))]
197195
#[non_exhaustive]
198196
pub struct TimeseriesOptions {
199197
/// Name of the top-level field to be used for time. Inserted documents must have this field,
@@ -208,6 +206,31 @@ pub struct TimeseriesOptions {
208206
/// The units you'd use to describe the expected interval between subsequent measurements for a
209207
/// time-series. Defaults to `TimeseriesGranularity::Seconds` if unset.
210208
pub granularity: Option<TimeseriesGranularity>,
209+
210+
/// The maximum time between timestamps in the same bucket. This value must be between 1 and
211+
/// 31,536,000 seconds. If this value is set, the same value should be set for
212+
/// `bucket_rounding` and `granularity` should not be set.
213+
///
214+
/// This option is only available on MongoDB 6.3+.
215+
#[serde(
216+
default,
217+
with = "serde_util::duration_option_as_int_seconds",
218+
rename = "bucketMaxSpanSeconds"
219+
)]
220+
pub bucket_max_span: Option<Duration>,
221+
222+
/// The time interval that determines the starting timestamp for a new bucket. When a document
223+
/// requires a new bucket, MongoDB rounds down the document's timestamp value by this interval
224+
/// to set the minimum time for the bucket. If this value is set, the same value should be set
225+
/// for `bucket_max_span` and `granularity` should not be set.
226+
///
227+
/// This option is only available on MongoDB 6.3+.
228+
#[serde(
229+
default,
230+
with = "serde_util::duration_option_as_int_seconds",
231+
rename = "bucketRoundingSeconds"
232+
)]
233+
pub bucket_rounding: Option<Duration>,
211234
}
212235

213236
/// The units you'd use to describe the expected interval between subsequent measurements for a

src/index/options.rs

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -29,8 +29,7 @@ pub struct IndexOptions {
2929
#[serde(
3030
rename = "expireAfterSeconds",
3131
default,
32-
deserialize_with = "serde_util::deserialize_duration_option_from_u64_seconds",
33-
serialize_with = "serde_util::serialize_duration_option_as_int_secs"
32+
with = "serde_util::duration_option_as_int_seconds"
3433
)]
3534
pub expire_after: Option<Duration>,
3635

src/selection_criteria.rs

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -277,8 +277,7 @@ pub struct ReadPreferenceOptions {
277277
#[serde(
278278
rename = "maxStalenessSeconds",
279279
default,
280-
deserialize_with = "serde_util::deserialize_duration_option_from_u64_seconds",
281-
serialize_with = "serde_util::serialize_duration_option_as_int_secs"
280+
with = "serde_util::duration_option_as_int_seconds"
282281
)]
283282
pub max_staleness: Option<Duration>,
284283

@@ -388,7 +387,7 @@ impl ReadPreference {
388387

389388
readpreferencetags: Option<&'a Vec<HashMap<String, String>>>,
390389

391-
#[serde(serialize_with = "serde_util::serialize_duration_option_as_int_secs")]
390+
#[serde(serialize_with = "serde_util::duration_option_as_int_seconds::serialize")]
392391
maxstalenessseconds: Option<Duration>,
393392
}
394393

src/serde_util.rs

Lines changed: 27 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -8,28 +8,42 @@ use crate::{
88
error::{Error, Result},
99
};
1010

11-
pub(crate) fn serialize_duration_option_as_int_millis<S: Serializer>(
12-
val: &Option<Duration>,
13-
serializer: S,
14-
) -> std::result::Result<S::Ok, S::Error> {
15-
match val {
16-
Some(duration) if duration.as_millis() > i32::MAX as u128 => {
17-
serializer.serialize_i64(duration.as_millis() as i64)
11+
pub(crate) mod duration_option_as_int_seconds {
12+
use super::*;
13+
14+
pub(crate) fn serialize<S: Serializer>(
15+
val: &Option<Duration>,
16+
serializer: S,
17+
) -> std::result::Result<S::Ok, S::Error> {
18+
match val {
19+
Some(duration) if duration.as_secs() > i32::MAX as u64 => {
20+
serializer.serialize_i64(duration.as_secs() as i64)
21+
}
22+
Some(duration) => serializer.serialize_i32(duration.as_secs() as i32),
23+
None => serializer.serialize_none(),
1824
}
19-
Some(duration) => serializer.serialize_i32(duration.as_millis() as i32),
20-
None => serializer.serialize_none(),
25+
}
26+
27+
pub(crate) fn deserialize<'de, D>(
28+
deserializer: D,
29+
) -> std::result::Result<Option<Duration>, D::Error>
30+
where
31+
D: Deserializer<'de>,
32+
{
33+
let millis = Option::<u64>::deserialize(deserializer)?;
34+
Ok(millis.map(Duration::from_secs))
2135
}
2236
}
2337

24-
pub(crate) fn serialize_duration_option_as_int_secs<S: Serializer>(
38+
pub(crate) fn serialize_duration_option_as_int_millis<S: Serializer>(
2539
val: &Option<Duration>,
2640
serializer: S,
2741
) -> std::result::Result<S::Ok, S::Error> {
2842
match val {
29-
Some(duration) if duration.as_secs() > i32::MAX as u64 => {
30-
serializer.serialize_i64(duration.as_secs() as i64)
43+
Some(duration) if duration.as_millis() > i32::MAX as u128 => {
44+
serializer.serialize_i64(duration.as_millis() as i64)
3145
}
32-
Some(duration) => serializer.serialize_i32(duration.as_secs() as i32),
46+
Some(duration) => serializer.serialize_i32(duration.as_millis() as i32),
3347
None => serializer.serialize_none(),
3448
}
3549
}
@@ -44,16 +58,6 @@ where
4458
Ok(millis.map(Duration::from_millis))
4559
}
4660

47-
pub(crate) fn deserialize_duration_option_from_u64_seconds<'de, D>(
48-
deserializer: D,
49-
) -> std::result::Result<Option<Duration>, D::Error>
50-
where
51-
D: Deserializer<'de>,
52-
{
53-
let millis = Option::<u64>::deserialize(deserializer)?;
54-
Ok(millis.map(Duration::from_secs))
55-
}
56-
5761
#[allow(clippy::trivially_copy_pass_by_ref)]
5862
pub(crate) fn serialize_u32_option_as_i32<S: Serializer>(
5963
val: &Option<u32>,

src/test/spec/collection_management.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,6 @@ async fn run_unified() {
66
let _guard = LOCK.run_exclusively().await;
77
run_unified_tests(&["collection-management"])
88
// The driver does not support modifyCollection.
9-
.skip_files(&["modifyCollection-pre_and_post_images.json"])
9+
.skip_files(&["modifyCollection-pre_and_post_images.json", "modifyCollection-errorResponse.json"])
1010
.await;
1111
}
Lines changed: 118 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,118 @@
1+
{
2+
"description": "modifyCollection-errorResponse",
3+
"schemaVersion": "1.12",
4+
"createEntities": [
5+
{
6+
"client": {
7+
"id": "client0",
8+
"observeEvents": [
9+
"commandStartedEvent"
10+
]
11+
}
12+
},
13+
{
14+
"database": {
15+
"id": "database0",
16+
"client": "client0",
17+
"databaseName": "collMod-tests"
18+
}
19+
},
20+
{
21+
"collection": {
22+
"id": "collection0",
23+
"database": "database0",
24+
"collectionName": "test"
25+
}
26+
}
27+
],
28+
"initialData": [
29+
{
30+
"collectionName": "test",
31+
"databaseName": "collMod-tests",
32+
"documents": [
33+
{
34+
"_id": 1,
35+
"x": 1
36+
},
37+
{
38+
"_id": 2,
39+
"x": 1
40+
}
41+
]
42+
}
43+
],
44+
"tests": [
45+
{
46+
"description": "modifyCollection prepareUnique violations are accessible",
47+
"runOnRequirements": [
48+
{
49+
"minServerVersion": "5.2"
50+
}
51+
],
52+
"operations": [
53+
{
54+
"name": "createIndex",
55+
"object": "collection0",
56+
"arguments": {
57+
"keys": {
58+
"x": 1
59+
}
60+
}
61+
},
62+
{
63+
"name": "modifyCollection",
64+
"object": "database0",
65+
"arguments": {
66+
"collection": "test",
67+
"index": {
68+
"keyPattern": {
69+
"x": 1
70+
},
71+
"prepareUnique": true
72+
}
73+
}
74+
},
75+
{
76+
"name": "insertOne",
77+
"object": "collection0",
78+
"arguments": {
79+
"document": {
80+
"_id": 3,
81+
"x": 1
82+
}
83+
},
84+
"expectError": {
85+
"errorCode": 11000
86+
}
87+
},
88+
{
89+
"name": "modifyCollection",
90+
"object": "database0",
91+
"arguments": {
92+
"collection": "test",
93+
"index": {
94+
"keyPattern": {
95+
"x": 1
96+
},
97+
"unique": true
98+
}
99+
},
100+
"expectError": {
101+
"isClientError": false,
102+
"errorCode": 359,
103+
"errorResponse": {
104+
"violations": [
105+
{
106+
"ids": [
107+
1,
108+
2
109+
]
110+
}
111+
]
112+
}
113+
}
114+
}
115+
]
116+
}
117+
]
118+
}
Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
description: "modifyCollection-errorResponse"
2+
3+
schemaVersion: "1.12"
4+
5+
createEntities:
6+
- client:
7+
id: &client0 client0
8+
observeEvents: [ commandStartedEvent ]
9+
- database:
10+
id: &database0 database0
11+
client: *client0
12+
databaseName: &database0Name collMod-tests
13+
- collection:
14+
id: &collection0 collection0
15+
database: *database0
16+
collectionName: &collection0Name test
17+
18+
initialData: &initialData
19+
- collectionName: *collection0Name
20+
databaseName: *database0Name
21+
documents:
22+
- { _id: 1, x: 1 }
23+
- { _id: 2, x: 1 }
24+
25+
tests:
26+
- description: "modifyCollection prepareUnique violations are accessible"
27+
runOnRequirements:
28+
- minServerVersion: "5.2" # SERVER-61158
29+
operations:
30+
- name: createIndex
31+
object: *collection0
32+
arguments:
33+
keys: { x: 1 }
34+
- name: modifyCollection
35+
object: *database0
36+
arguments:
37+
collection: *collection0Name
38+
index:
39+
keyPattern: { x: 1 }
40+
prepareUnique: true
41+
- name: insertOne
42+
object: *collection0
43+
arguments:
44+
document: { _id: 3, x: 1 }
45+
expectError:
46+
errorCode: 11000 # DuplicateKey
47+
- name: modifyCollection
48+
object: *database0
49+
arguments:
50+
collection: *collection0Name
51+
index:
52+
keyPattern: { x: 1 }
53+
unique: true
54+
expectError:
55+
isClientError: false
56+
errorCode: 359 # CannotConvertIndexToUnique
57+
errorResponse:
58+
violations:
59+
- { ids: [ 1, 2 ] }

0 commit comments

Comments
 (0)