Skip to content

RUST-1129 Make the dependency on chrono optional and provide equivalent time interop #352

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

Merged
merged 18 commits into from
Apr 15, 2022

Conversation

abr-egn
Copy link
Contributor

@abr-egn abr-egn commented Apr 5, 2022

RUST-1129

This PR swaps out the internal operations that used chrono for equivalent ones using time, makes the chrono dependency optional, and adds optional interop methods for time structs.

src/datetime.rs Outdated
use std::result::Result;
match self.to_time().format(&Rfc3339) {
Result::Ok(s) => s,
Result::Err(e) => e.to_string(),
Copy link
Contributor Author

@abr-egn abr-egn Apr 5, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not thrilled about handling the error this way but I couldn't think of a better option that wouldn't be a breaking change.

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

given that the chrono equivalent never errored, any idea what can lead this method to fail / how likely we are to encounter this?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

From the source, the time method fails if the year is outside [0,9999]; the chrono equivalent doesn't have that check.

The time method has some other potential failure cases as well since it's a more general method, but for the specific purpose of formatting an OffsetDateTime I don't think they can happen.

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

hmmm, should we consider deprecating this method in favor of a new version that propagates the error correctly? seems like it might help cut down on the chances anyone hits this weirdness

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The time crate actually has a large-dates flag which expands this to +-99,999, which we may want to consider enabling here, as MongoDB technically supports any date that can be represented as an i64 from the Unix epoch. Should we enable that flag, we'll want to let the maintainer know of our use case here so that the feature flag stays around in the future.

Also, rather than erroring, it may be more appropriate to panic here. Could we test some inputs that error in time and see if they panic in chrono? If they do it might be okay for us to panic here as well. Another option would be to cap the date to time's maximum value and use that for the date here. I think we do that in a few places already with chrono's maximum date. If none of those work, then I agree we probably should deprecate this method and introduce a new one, since I think including error information as a String could be pretty cumbersome or dangerous.

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

that solution SGTM. since we use the flag already it probably seems worth chiming in on that discussion linked above to let them know why

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

And as a last check, are we okay with the risk of breaking user code with this? It's technically possible that a user has a super large date in their DB somewhere that used to format okay but will panic now via this method or fail to serialize via the serde helper.

Copy link
Contributor Author

@abr-egn abr-egn Apr 13, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think the risk is okay here - we're being loud about this change with the deprecation warning, and I think we should also call it out in the patch notes. It's also worth considering that the RFC itself is explicit about only applying to years in the 0000-9999 range and that the year field is fixed at four digits, so the chrono implementation is producing output that's probably not going to be accepted elsewhere.

That said, I'd be interested to hear if @kmahar or @isabelatkinson have other opinions.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oh nice, I didn't know that about the RFC, that definitely makes this change seem fine to me then.

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

agree it seems fine 👍

})?
.into();
Ok(crate::DateTime::from_chrono(datetime))
let datetime = crate::DateTime::parse_rfc3339_str(date.as_str()).map_err(|_| {
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Here and elsewhere I changed it from the "parse into external datetime, convert to bson datetime" pattern to just directly using the methods bson::DateTime provides.

src/bson.rs Outdated
@@ -430,7 +437,7 @@ impl Bson {
})
}
Bson::ObjectId(v) => json!({"$oid": v.to_hex()}),
Bson::DateTime(v) if v.timestamp_millis() >= 0 && v.to_chrono().year() <= 99999 => {
Bson::DateTime(v) if v.timestamp_millis() >= 0 && v.to_time().year() <= 99999 => {
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not sure if the year constraint here is correct - it's different than the one below.

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

from my reading of the extJSON spec we should drop a 9 here to match below... see datetime entries in this table.

I was wondering how we would pass the corresponding corpus test, but it looks like that test case does not have a relaxed extJSON version specified so we likely make no assertions on it -- maybe it's omitted because it's the same as the canonical, but IMO it seems like test runners should probably confirm their relaxed output in that case matches canonical. perhaps we should add an assertion to our corpus runner so that for a test case with no expected relaxed output, we check that the relaxed output is equal to the expected canonical output?

if that just works, I'd propose we file a DRIVERS ticket that suggests amending the corpus spec to require this assertion.

thoughts?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It looks like that doesn't just work, unfortunately. I filed RUST-1261 to follow up on that.

@abr-egn abr-egn marked this pull request as ready for review April 6, 2022 17:45
Copy link

@kmahar kmahar left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

looking good! just a few questions/thoughts

src/bson.rs Outdated
@@ -430,7 +437,7 @@ impl Bson {
})
}
Bson::ObjectId(v) => json!({"$oid": v.to_hex()}),
Bson::DateTime(v) if v.timestamp_millis() >= 0 && v.to_chrono().year() <= 99999 => {
Bson::DateTime(v) if v.timestamp_millis() >= 0 && v.to_time().year() <= 99999 => {
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

from my reading of the extJSON spec we should drop a 9 here to match below... see datetime entries in this table.

I was wondering how we would pass the corresponding corpus test, but it looks like that test case does not have a relaxed extJSON version specified so we likely make no assertions on it -- maybe it's omitted because it's the same as the canonical, but IMO it seems like test runners should probably confirm their relaxed output in that case matches canonical. perhaps we should add an assertion to our corpus runner so that for a test case with no expected relaxed output, we check that the relaxed output is equal to the expected canonical output?

if that just works, I'd propose we file a DRIVERS ticket that suggests amending the corpus spec to require this assertion.

thoughts?

src/datetime.rs Outdated
use std::result::Result;
match self.to_time().format(&Rfc3339) {
Result::Ok(s) => s,
Result::Err(e) => e.to_string(),
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

given that the chrono equivalent never errored, any idea what can lead this method to fail / how likely we are to encounter this?

src/datetime.rs Outdated
use std::result::Result;
match self.to_time().format(&Rfc3339) {
Result::Ok(s) => s,
Result::Err(e) => e.to_string(),
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The time crate actually has a large-dates flag which expands this to +-99,999, which we may want to consider enabling here, as MongoDB technically supports any date that can be represented as an i64 from the Unix epoch. Should we enable that flag, we'll want to let the maintainer know of our use case here so that the feature flag stays around in the future.

Also, rather than erroring, it may be more appropriate to panic here. Could we test some inputs that error in time and see if they panic in chrono? If they do it might be okay for us to panic here as well. Another option would be to cap the date to time's maximum value and use that for the date here. I think we do that in a few places already with chrono's maximum date. If none of those work, then I agree we probably should deprecate this method and introduce a new one, since I think including error information as a String could be pretty cumbersome or dangerous.

src/datetime.rs Outdated
use std::result::Result;
match self.to_time().format(&Rfc3339) {
Result::Ok(s) => s,
Result::Err(e) => e.to_string(),
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

And as a last check, are we okay with the risk of breaking user code with this? It's technically possible that a user has a super large date in their DB somewhere that used to format okay but will panic now via this method or fail to serialize via the serde helper.

Copy link
Contributor

@isabelatkinson isabelatkinson left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

lgtm mod patrick's renaming comment!

@abr-egn abr-egn merged commit b62338a into mongodb:main Apr 15, 2022
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants