Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

RUST-1687 Add human_readable_serialization option to Collection #902

Merged
24 changes: 19 additions & 5 deletions src/coll.rs
Original file line number Diff line number Diff line change
Expand Up @@ -143,6 +143,7 @@ struct CollectionInner {
selection_criteria: Option<SelectionCriteria>,
read_concern: Option<ReadConcern>,
write_concern: Option<WriteConcern>,
human_readable_serialization: bool,
}

impl<T> Collection<T> {
Expand All @@ -157,6 +158,7 @@ impl<T> Collection<T> {
let write_concern = options
.write_concern
.or_else(|| db.write_concern().cloned());
let human_readable_serialization = options.human_readable_serialization.unwrap_or_default();

Self {
inner: Arc::new(CollectionInner {
Expand All @@ -166,6 +168,7 @@ impl<T> Collection<T> {
selection_criteria,
read_concern,
write_concern,
human_readable_serialization,
}),
_phantom: Default::default(),
}
Expand Down Expand Up @@ -1119,14 +1122,16 @@ where
options: impl Into<Option<FindOneAndReplaceOptions>>,
session: impl Into<Option<&mut ClientSession>>,
) -> Result<Option<T>> {
let mut options = options.into();
let replacement = to_document_with_options(
replacement.borrow(),
SerializerOptions::builder().human_readable(false).build(),
SerializerOptions::builder()
.human_readable(self.inner.human_readable_serialization)
.build(),
)?;

let session = session.into();

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

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

while n_attempted < ds.len() {
let docs: Vec<&T> = ds.iter().skip(n_attempted).map(Borrow::borrow).collect();
let insert = Insert::new_encrypted(self.namespace(), docs, options.clone(), encrypted);
let insert = Insert::new_encrypted(
self.namespace(),
docs,
options.clone(),
encrypted,
self.inner.human_readable_serialization,
);

match self
.client()
Expand Down Expand Up @@ -1332,6 +1343,7 @@ where
self.namespace(),
vec![doc],
options.map(InsertManyOptions::from_insert_one_options),
self.inner.human_readable_serialization,
);
self.client()
.execute_operation(insert, session)
Expand Down Expand Up @@ -1382,16 +1394,18 @@ where
options: impl Into<Option<ReplaceOptions>>,
session: impl Into<Option<&mut ClientSession>>,
) -> Result<UpdateResult> {
let mut options = options.into();
let replacement = to_document_with_options(
replacement.borrow(),
SerializerOptions::builder().human_readable(false).build(),
SerializerOptions::builder()
.human_readable(self.inner.human_readable_serialization)
.build(),
)?;

bson_util::replacement_document_check(&replacement)?;

let session = session.into();

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

let update = Update::new(
Expand Down
5 changes: 5 additions & 0 deletions src/coll/options.rs
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,11 @@ pub struct CollectionOptions {

/// The default write concern for operations.
pub write_concern: Option<WriteConcern>,

/// Sets the [`bson::SerializerOptions::human_readable`] option for the [`Bson`] serializer.
/// The default value is `false`.
maiconpavi marked this conversation as resolved.
Show resolved Hide resolved
/// Note: Specifying `true` for this value will decrease the performance of insert operations.
pub human_readable_serialization: Option<bool>,
}

/// Specifies whether a
Expand Down
18 changes: 16 additions & 2 deletions src/operation/insert.rs
Original file line number Diff line number Diff line change
Expand Up @@ -31,29 +31,33 @@ pub(crate) struct Insert<'a, T> {
inserted_ids: Vec<Bson>,
options: Option<InsertManyOptions>,
encrypted: bool,
human_readable_serialization: bool,
}

impl<'a, T> Insert<'a, T> {
pub(crate) fn new(
ns: Namespace,
documents: Vec<&'a T>,
options: Option<InsertManyOptions>,
human_readable_serialization: bool,
) -> Self {
Self::new_encrypted(ns, documents, options, false)
Self::new_encrypted(ns, documents, options, false, human_readable_serialization)
}

pub(crate) fn new_encrypted(
ns: Namespace,
documents: Vec<&'a T>,
options: Option<InsertManyOptions>,
encrypted: bool,
human_readable_serialization: bool,
) -> Self {
Self {
ns,
options,
documents,
inserted_ids: vec![],
encrypted,
human_readable_serialization,
}
}

Expand Down Expand Up @@ -82,7 +86,17 @@ impl<'a, T: Serialize> OperationWithDefaults for Insert<'a, T> {
.take(description.max_write_batch_size as usize)
.enumerate()
{
let mut doc = bson::to_raw_document_buf(d)?;
let mut doc = if self.human_readable_serialization {
let serializer_options = bson::SerializerOptions::builder()
.human_readable(true)
.build();
bson::RawDocumentBuf::from_document(&bson::to_document_with_options(
d,
serializer_options,
)?)?
} else {
bson::to_raw_document_buf(d)?
};
let id = match doc.get("_id")? {
Some(b) => b.try_into()?,
None => {
Expand Down
10 changes: 7 additions & 3 deletions src/operation/insert/test.rs
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@ fn fixtures(opts: Option<InsertManyOptions>) -> TestFixtures {
},
DOCUMENTS.iter().collect(),
Some(options.clone()),
false,
);

TestFixtures {
Expand Down Expand Up @@ -116,7 +117,7 @@ fn build() {
#[test]
fn build_ordered() {
let docs = vec![Document::new()];
let mut insert = Insert::new(Namespace::empty(), docs.iter().collect(), None);
let mut insert = Insert::new(Namespace::empty(), docs.iter().collect(), None, false);
let cmd = insert
.build(&StreamDescription::new_testing())
.expect("should succeed");
Expand All @@ -128,6 +129,7 @@ fn build_ordered() {
Namespace::empty(),
docs.iter().collect(),
Some(InsertManyOptions::builder().ordered(false).build()),
false,
);
let cmd = insert
.build(&StreamDescription::new_testing())
Expand All @@ -140,6 +142,7 @@ fn build_ordered() {
Namespace::empty(),
docs.iter().collect(),
Some(InsertManyOptions::builder().ordered(true).build()),
false,
);
let cmd = insert
.build(&StreamDescription::new_testing())
Expand All @@ -152,6 +155,7 @@ fn build_ordered() {
Namespace::empty(),
docs.iter().collect(),
Some(InsertManyOptions::builder().build()),
false,
);
let cmd = insert
.build(&StreamDescription::new_testing())
Expand All @@ -170,7 +174,7 @@ struct Documents<D> {
fn generate_ids() {
let docs = vec![doc! { "x": 1 }, doc! { "_id": 1_i32, "x": 2 }];

let mut insert = Insert::new(Namespace::empty(), docs.iter().collect(), None);
let mut insert = Insert::new(Namespace::empty(), docs.iter().collect(), None, false);
let cmd = insert.build(&StreamDescription::new_testing()).unwrap();
let serialized = insert.serialize_command(cmd).unwrap();

Expand Down Expand Up @@ -253,7 +257,7 @@ fn serialize_all_types() {
"_id": ObjectId::new(),
}];

let mut insert = Insert::new(Namespace::empty(), docs.iter().collect(), None);
let mut insert = Insert::new(Namespace::empty(), docs.iter().collect(), None, false);
let cmd = insert.build(&StreamDescription::new_testing()).unwrap();
let serialized = insert.serialize_command(cmd).unwrap();
let cmd: Documents<Document> = bson::from_slice(serialized.as_slice()).unwrap();
Expand Down
121 changes: 121 additions & 0 deletions src/test/coll.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1214,3 +1214,124 @@ fn test_namespace_fromstr() {
assert_eq!(t.db, "something");
assert_eq!(t.coll, "something.else");
}

#[cfg_attr(feature = "tokio-runtime", tokio::test)]
#[cfg_attr(feature = "async-std-runtime", async_std::test)]
async fn configure_human_readable_serialization() {
#[derive(Deserialize)]
struct StringOrBytes(String);

impl Serialize for StringOrBytes {
fn serialize<S>(&self, serializer: S) -> std::result::Result<S::Ok, S::Error>
where
S: serde::Serializer,
{
if serializer.is_human_readable() {
serializer.serialize_str(&self.0)
} else {
serializer.serialize_bytes(self.0.as_bytes())
}
}
}

#[derive(Deserialize, Serialize)]
struct Data {
id: u32,
s: StringOrBytes,
}

let client = TestClient::new().await;

let collection_options = CollectionOptions::builder()
.human_readable_serialization(false)
.build();

let non_human_readable_collection: Collection<Data> = client
.database("db")
.collection_with_options("nonhumanreadable", collection_options);
non_human_readable_collection.drop(None).await.unwrap();

non_human_readable_collection
.insert_one(
Data {
id: 0,
s: StringOrBytes("non human readable!".into()),
},
None,
)
.await
.unwrap();

// The inserted bytes will not deserialize to StringOrBytes properly, so find as a document
// instead.
let document_collection = non_human_readable_collection.clone_with_type::<Document>();
let doc = document_collection
.find_one(doc! { "id": 0 }, None)
.await
.unwrap()
.unwrap();
assert!(doc.get_binary_generic("s").is_ok());

non_human_readable_collection
.replace_one(
doc! { "id": 0 },
Data {
id: 1,
s: StringOrBytes("non human readable!".into()),
},
None,
)
.await
.unwrap();

let doc = document_collection
.find_one(doc! { "id": 1 }, None)
.await
.unwrap()
.unwrap();
assert!(doc.get_binary_generic("s").is_ok());

let collection_options = CollectionOptions::builder()
.human_readable_serialization(true)
.build();

let human_readable_collection: Collection<Data> = client
.database("db")
.collection_with_options("humanreadable", collection_options);
human_readable_collection.drop(None).await.unwrap();

human_readable_collection
.insert_one(
Data {
id: 0,
s: StringOrBytes("human readable!".into()),
},
None,
)
.await
.unwrap();

// Proper deserialization to a string demonstrates that the data was correctly serialized as a
// string.
human_readable_collection
.find_one(doc! { "id": 0 }, None)
.await
.unwrap();

human_readable_collection
.replace_one(
doc! { "id": 0 },
Data {
id: 1,
s: StringOrBytes("human readable!".into()),
},
None,
)
.await
.unwrap();

human_readable_collection
.find_one(doc! { "id": 1 }, None)
.await
.unwrap();
}
1 change: 1 addition & 0 deletions src/test/spec/unified_runner/test_file.rs
Original file line number Diff line number Diff line change
Expand Up @@ -378,6 +378,7 @@ impl CollectionOrDatabaseOptions {
read_concern: self.read_concern.clone(),
selection_criteria: self.selection_criteria.clone(),
write_concern: self.write_concern.clone(),
human_readable_serialization: None,
}
}
}
Expand Down