Skip to content

Commit f89cc1a

Browse files
RUST-1687 Add human_readable_serialization option to Collection (#902)
Co-authored-by: Isabel Atkinson <[email protected]>
1 parent 56011b9 commit f89cc1a

File tree

6 files changed

+169
-10
lines changed

6 files changed

+169
-10
lines changed

src/coll.rs

Lines changed: 19 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -143,6 +143,7 @@ struct CollectionInner {
143143
selection_criteria: Option<SelectionCriteria>,
144144
read_concern: Option<ReadConcern>,
145145
write_concern: Option<WriteConcern>,
146+
human_readable_serialization: bool,
146147
}
147148

148149
impl<T> Collection<T> {
@@ -157,6 +158,7 @@ impl<T> Collection<T> {
157158
let write_concern = options
158159
.write_concern
159160
.or_else(|| db.write_concern().cloned());
161+
let human_readable_serialization = options.human_readable_serialization.unwrap_or_default();
160162

161163
Self {
162164
inner: Arc::new(CollectionInner {
@@ -166,6 +168,7 @@ impl<T> Collection<T> {
166168
selection_criteria,
167169
read_concern,
168170
write_concern,
171+
human_readable_serialization,
169172
}),
170173
_phantom: Default::default(),
171174
}
@@ -1119,14 +1122,16 @@ where
11191122
options: impl Into<Option<FindOneAndReplaceOptions>>,
11201123
session: impl Into<Option<&mut ClientSession>>,
11211124
) -> Result<Option<T>> {
1125+
let mut options = options.into();
11221126
let replacement = to_document_with_options(
11231127
replacement.borrow(),
1124-
SerializerOptions::builder().human_readable(false).build(),
1128+
SerializerOptions::builder()
1129+
.human_readable(self.inner.human_readable_serialization)
1130+
.build(),
11251131
)?;
11261132

11271133
let session = session.into();
11281134

1129-
let mut options = options.into();
11301135
resolve_write_concern_with_session!(self, options, session.as_ref())?;
11311136

11321137
let op = FindAndModify::<T>::with_replace(self.namespace(), filter, replacement, options)?;
@@ -1205,7 +1210,13 @@ where
12051210

12061211
while n_attempted < ds.len() {
12071212
let docs: Vec<&T> = ds.iter().skip(n_attempted).map(Borrow::borrow).collect();
1208-
let insert = Insert::new_encrypted(self.namespace(), docs, options.clone(), encrypted);
1213+
let insert = Insert::new_encrypted(
1214+
self.namespace(),
1215+
docs,
1216+
options.clone(),
1217+
encrypted,
1218+
self.inner.human_readable_serialization,
1219+
);
12091220

12101221
match self
12111222
.client()
@@ -1332,6 +1343,7 @@ where
13321343
self.namespace(),
13331344
vec![doc],
13341345
options.map(InsertManyOptions::from_insert_one_options),
1346+
self.inner.human_readable_serialization,
13351347
);
13361348
self.client()
13371349
.execute_operation(insert, session)
@@ -1382,16 +1394,18 @@ where
13821394
options: impl Into<Option<ReplaceOptions>>,
13831395
session: impl Into<Option<&mut ClientSession>>,
13841396
) -> Result<UpdateResult> {
1397+
let mut options = options.into();
13851398
let replacement = to_document_with_options(
13861399
replacement.borrow(),
1387-
SerializerOptions::builder().human_readable(false).build(),
1400+
SerializerOptions::builder()
1401+
.human_readable(self.inner.human_readable_serialization)
1402+
.build(),
13881403
)?;
13891404

13901405
bson_util::replacement_document_check(&replacement)?;
13911406

13921407
let session = session.into();
13931408

1394-
let mut options = options.into();
13951409
resolve_write_concern_with_session!(self, options, session.as_ref())?;
13961410

13971411
let update = Update::new(

src/coll/options.rs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,11 @@ pub struct CollectionOptions {
2828

2929
/// The default write concern for operations.
3030
pub write_concern: Option<WriteConcern>,
31+
32+
/// Sets the [`bson::SerializerOptions::human_readable`] option for the [`Bson`] serializer.
33+
/// The default value is `false`.
34+
/// Note: Specifying `true` for this value will decrease the performance of insert operations.
35+
pub human_readable_serialization: Option<bool>,
3136
}
3237

3338
/// Specifies whether a

src/operation/insert.rs

Lines changed: 16 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -31,29 +31,33 @@ pub(crate) struct Insert<'a, T> {
3131
inserted_ids: Vec<Bson>,
3232
options: Option<InsertManyOptions>,
3333
encrypted: bool,
34+
human_readable_serialization: bool,
3435
}
3536

3637
impl<'a, T> Insert<'a, T> {
3738
pub(crate) fn new(
3839
ns: Namespace,
3940
documents: Vec<&'a T>,
4041
options: Option<InsertManyOptions>,
42+
human_readable_serialization: bool,
4143
) -> Self {
42-
Self::new_encrypted(ns, documents, options, false)
44+
Self::new_encrypted(ns, documents, options, false, human_readable_serialization)
4345
}
4446

4547
pub(crate) fn new_encrypted(
4648
ns: Namespace,
4749
documents: Vec<&'a T>,
4850
options: Option<InsertManyOptions>,
4951
encrypted: bool,
52+
human_readable_serialization: bool,
5053
) -> Self {
5154
Self {
5255
ns,
5356
options,
5457
documents,
5558
inserted_ids: vec![],
5659
encrypted,
60+
human_readable_serialization,
5761
}
5862
}
5963

@@ -82,7 +86,17 @@ impl<'a, T: Serialize> OperationWithDefaults for Insert<'a, T> {
8286
.take(description.max_write_batch_size as usize)
8387
.enumerate()
8488
{
85-
let mut doc = bson::to_raw_document_buf(d)?;
89+
let mut doc = if self.human_readable_serialization {
90+
let serializer_options = bson::SerializerOptions::builder()
91+
.human_readable(true)
92+
.build();
93+
bson::RawDocumentBuf::from_document(&bson::to_document_with_options(
94+
d,
95+
serializer_options,
96+
)?)?
97+
} else {
98+
bson::to_raw_document_buf(d)?
99+
};
86100
let id = match doc.get("_id")? {
87101
Some(b) => b.try_into()?,
88102
None => {

src/operation/insert/test.rs

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,7 @@ fn fixtures(opts: Option<InsertManyOptions>) -> TestFixtures {
4949
},
5050
DOCUMENTS.iter().collect(),
5151
Some(options.clone()),
52+
false,
5253
);
5354

5455
TestFixtures {
@@ -116,7 +117,7 @@ fn build() {
116117
#[test]
117118
fn build_ordered() {
118119
let docs = vec![Document::new()];
119-
let mut insert = Insert::new(Namespace::empty(), docs.iter().collect(), None);
120+
let mut insert = Insert::new(Namespace::empty(), docs.iter().collect(), None, false);
120121
let cmd = insert
121122
.build(&StreamDescription::new_testing())
122123
.expect("should succeed");
@@ -128,6 +129,7 @@ fn build_ordered() {
128129
Namespace::empty(),
129130
docs.iter().collect(),
130131
Some(InsertManyOptions::builder().ordered(false).build()),
132+
false,
131133
);
132134
let cmd = insert
133135
.build(&StreamDescription::new_testing())
@@ -140,6 +142,7 @@ fn build_ordered() {
140142
Namespace::empty(),
141143
docs.iter().collect(),
142144
Some(InsertManyOptions::builder().ordered(true).build()),
145+
false,
143146
);
144147
let cmd = insert
145148
.build(&StreamDescription::new_testing())
@@ -152,6 +155,7 @@ fn build_ordered() {
152155
Namespace::empty(),
153156
docs.iter().collect(),
154157
Some(InsertManyOptions::builder().build()),
158+
false,
155159
);
156160
let cmd = insert
157161
.build(&StreamDescription::new_testing())
@@ -170,7 +174,7 @@ struct Documents<D> {
170174
fn generate_ids() {
171175
let docs = vec![doc! { "x": 1 }, doc! { "_id": 1_i32, "x": 2 }];
172176

173-
let mut insert = Insert::new(Namespace::empty(), docs.iter().collect(), None);
177+
let mut insert = Insert::new(Namespace::empty(), docs.iter().collect(), None, false);
174178
let cmd = insert.build(&StreamDescription::new_testing()).unwrap();
175179
let serialized = insert.serialize_command(cmd).unwrap();
176180

@@ -253,7 +257,7 @@ fn serialize_all_types() {
253257
"_id": ObjectId::new(),
254258
}];
255259

256-
let mut insert = Insert::new(Namespace::empty(), docs.iter().collect(), None);
260+
let mut insert = Insert::new(Namespace::empty(), docs.iter().collect(), None, false);
257261
let cmd = insert.build(&StreamDescription::new_testing()).unwrap();
258262
let serialized = insert.serialize_command(cmd).unwrap();
259263
let cmd: Documents<Document> = bson::from_slice(serialized.as_slice()).unwrap();

src/test/coll.rs

Lines changed: 121 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1214,3 +1214,124 @@ fn test_namespace_fromstr() {
12141214
assert_eq!(t.db, "something");
12151215
assert_eq!(t.coll, "something.else");
12161216
}
1217+
1218+
#[cfg_attr(feature = "tokio-runtime", tokio::test)]
1219+
#[cfg_attr(feature = "async-std-runtime", async_std::test)]
1220+
async fn configure_human_readable_serialization() {
1221+
#[derive(Deserialize)]
1222+
struct StringOrBytes(String);
1223+
1224+
impl Serialize for StringOrBytes {
1225+
fn serialize<S>(&self, serializer: S) -> std::result::Result<S::Ok, S::Error>
1226+
where
1227+
S: serde::Serializer,
1228+
{
1229+
if serializer.is_human_readable() {
1230+
serializer.serialize_str(&self.0)
1231+
} else {
1232+
serializer.serialize_bytes(self.0.as_bytes())
1233+
}
1234+
}
1235+
}
1236+
1237+
#[derive(Deserialize, Serialize)]
1238+
struct Data {
1239+
id: u32,
1240+
s: StringOrBytes,
1241+
}
1242+
1243+
let client = TestClient::new().await;
1244+
1245+
let collection_options = CollectionOptions::builder()
1246+
.human_readable_serialization(false)
1247+
.build();
1248+
1249+
let non_human_readable_collection: Collection<Data> = client
1250+
.database("db")
1251+
.collection_with_options("nonhumanreadable", collection_options);
1252+
non_human_readable_collection.drop(None).await.unwrap();
1253+
1254+
non_human_readable_collection
1255+
.insert_one(
1256+
Data {
1257+
id: 0,
1258+
s: StringOrBytes("non human readable!".into()),
1259+
},
1260+
None,
1261+
)
1262+
.await
1263+
.unwrap();
1264+
1265+
// The inserted bytes will not deserialize to StringOrBytes properly, so find as a document
1266+
// instead.
1267+
let document_collection = non_human_readable_collection.clone_with_type::<Document>();
1268+
let doc = document_collection
1269+
.find_one(doc! { "id": 0 }, None)
1270+
.await
1271+
.unwrap()
1272+
.unwrap();
1273+
assert!(doc.get_binary_generic("s").is_ok());
1274+
1275+
non_human_readable_collection
1276+
.replace_one(
1277+
doc! { "id": 0 },
1278+
Data {
1279+
id: 1,
1280+
s: StringOrBytes("non human readable!".into()),
1281+
},
1282+
None,
1283+
)
1284+
.await
1285+
.unwrap();
1286+
1287+
let doc = document_collection
1288+
.find_one(doc! { "id": 1 }, None)
1289+
.await
1290+
.unwrap()
1291+
.unwrap();
1292+
assert!(doc.get_binary_generic("s").is_ok());
1293+
1294+
let collection_options = CollectionOptions::builder()
1295+
.human_readable_serialization(true)
1296+
.build();
1297+
1298+
let human_readable_collection: Collection<Data> = client
1299+
.database("db")
1300+
.collection_with_options("humanreadable", collection_options);
1301+
human_readable_collection.drop(None).await.unwrap();
1302+
1303+
human_readable_collection
1304+
.insert_one(
1305+
Data {
1306+
id: 0,
1307+
s: StringOrBytes("human readable!".into()),
1308+
},
1309+
None,
1310+
)
1311+
.await
1312+
.unwrap();
1313+
1314+
// Proper deserialization to a string demonstrates that the data was correctly serialized as a
1315+
// string.
1316+
human_readable_collection
1317+
.find_one(doc! { "id": 0 }, None)
1318+
.await
1319+
.unwrap();
1320+
1321+
human_readable_collection
1322+
.replace_one(
1323+
doc! { "id": 0 },
1324+
Data {
1325+
id: 1,
1326+
s: StringOrBytes("human readable!".into()),
1327+
},
1328+
None,
1329+
)
1330+
.await
1331+
.unwrap();
1332+
1333+
human_readable_collection
1334+
.find_one(doc! { "id": 1 }, None)
1335+
.await
1336+
.unwrap();
1337+
}

src/test/spec/unified_runner/test_file.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -378,6 +378,7 @@ impl CollectionOrDatabaseOptions {
378378
read_concern: self.read_concern.clone(),
379379
selection_criteria: self.selection_criteria.clone(),
380380
write_concern: self.write_concern.clone(),
381+
human_readable_serialization: None,
381382
}
382383
}
383384
}

0 commit comments

Comments
 (0)