-
Notifications
You must be signed in to change notification settings - Fork 126
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
value: mark CqlValue as non_exhaustive #1257
Conversation
See the following report for details: cargo semver-checks output
|
0d9c84f
to
6b02be4
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
After moving displayer to scylla-cql, is it not possible to remove chrono
dependency from scylla
crate?
scylla-cql/src/value.rs
Outdated
CqlValue::Date(CqlDate(d)) => { | ||
// This is basically a copy of the code used in `impl TryInto<NaiveDate> for CqlDate` impl | ||
// in scylla-cql. We can't call this impl because it is behind chrono feature in scylla-cql. | ||
|
||
// date_days is u32 then converted to i64 then we subtract 2^31; | ||
// Max value is 2^31, min value is -2^31. Both values can safely fit in chrono::Duration, this call won't panic | ||
let days_since_epoch = | ||
chrono_04::Duration::try_days(*d as i64 - (1 << 31)).unwrap(); | ||
|
||
// TODO: chrono::NaiveDate does not handle the whole range | ||
// supported by the `date` datatype | ||
match chrono_04::NaiveDate::from_ymd_opt(1970, 1, 1) | ||
.unwrap() | ||
.checked_add_signed(days_since_epoch) | ||
{ | ||
Some(d) => write!(f, "'{}'", d)?, | ||
None => f.write_str("<date out of representable range>")?, | ||
} | ||
} | ||
CqlValue::Duration(d) => write!(f, "{}mo{}d{}ns", d.months, d.days, d.nanoseconds)?, | ||
CqlValue::Time(CqlTime(t)) => { | ||
write!( | ||
f, | ||
"'{:02}:{:02}:{:02}.{:09}'", | ||
t / 3_600_000_000_000, | ||
t / 60_000_000_000 % 60, | ||
t / 1_000_000_000 % 60, | ||
t % 1_000_000_000, | ||
)?; | ||
} | ||
CqlValue::Timestamp(CqlTimestamp(t)) => { | ||
match chrono_04::Utc.timestamp_millis_opt(*t) { | ||
chrono_04::LocalResult::Ambiguous(_, _) => unreachable!(), // not supposed to happen with timestamp_millis_opt | ||
chrono_04::LocalResult::Single(d) => { | ||
write!(f, "{}", d.format("'%Y-%m-%d %H:%M:%S%.3f%z'"))? | ||
} | ||
chrono_04::LocalResult::None => { | ||
f.write_str("<timestamp out of representable range>")? | ||
} | ||
} | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
One solution - admittedly not ideal - is to have separate display impls when the feature is enabled and not.
Something like this:
CqlValue::Date(CqlDate(d)) => {
#[cfg(feature = "chrono-04")]
{
// This is basically a copy of the code used in `impl TryInto<NaiveDate> for CqlDate` impl
// in scylla-cql. We can't call this impl because it is behind chrono feature in scylla-cql.
// date_days is u32 then converted to i64 then we subtract 2^31;
// Max value is 2^31, min value is -2^31. Both values can safely fit in chrono::Duration, this call won't panic
let days_since_epoch =
chrono_04::Duration::try_days(*d as i64 - (1 << 31)).unwrap();
// TODO: chrono::NaiveDate does not handle the whole range
// supported by the `date` datatype
match chrono_04::NaiveDate::from_ymd_opt(1970, 1, 1)
.unwrap()
.checked_add_signed(days_since_epoch)
{
Some(d) => write!(f, "'{}'", d)?,
None => f.write_str("<date out of representable range>")?,
}
}
#[cfg(not(feature = "chrono-04"))]
{
let days_since_epoch = *d as i64 - (1 << 31);
write!(f, "{} days from epoch", days_since_epoch)
}
}
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It allows to keep chrono optional, but has 2 downsides:
- if we add more chrono versions, then we will need to put more implementations here and select based on
cfg
expressions which I don't like. It may not be a big problem, as chrono seems to bump major version very rarely. - Worse display if the user doesn't know about this. We should put this in a visible place in docs.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think it's better to just have only one unconditional version of the dependency (current approach). This solution has some flaws, but at least simplifies a lot of things. But that's just my opinion.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🤔 Do I understand correctly that the approach taken in this PR is not in conflict with #771 because chrono
types do not appear in the public API, even though the chrono
dependency is used?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You are correct. We need the chrono
dependency unconditionally for internal usage - to be exact, for std::fmt::Display for CqlValue
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
OTOH, we expose chrono types in public API only when chrono-04
feature is enabled.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I wonder if we could prevent chrono from being used in public API unless the feature is activated.
One idea is to use cargo-public-api
in CI, without chrono feature, and verify that chrono::
string is not present in the output.
I checked it. We still use chrono in |
6b02be4
to
ae8cc53
Compare
v1.1: Moved pretty module to |
@muzarski Please fix the conflict, then I'll merge. |
Ref: https://docs.rs/itertools/latest/itertools/trait.Itertools.html#method.format I used ", " (with whitespace) for all metadata/tracing related pretty-printing - we already did that for some tablet related logs, so let's be consistent in other logs. I left "," (without whitespace) for displaying CqlValue. There is a test for that, and I think it makes sense.
itertools::Format implements Debug: https://docs.rs/itertools/latest/itertools/structs/struct.Format.html#impl-Debug-for-Format%3C'a,+I%3E.
It will be needed once I move the pretty module to scylla-cql.
I'm not a big fan of it - tbh, I hate that I have to do that. But we need this dependency for unconditional internal usage after the next commit (once I move `CqlValueDisplayer` to scylla-cql). Any ideas how to avoid this? As of now, we won't get any compile warnings when someone exposes chrono-04 type in public API without "chrono-04" feature enabled. In addition, I enabled `alloc` feature for chrono-04. It's required for https://docs.rs/chrono/latest/chrono/struct.DateTime.html#method.format.
Implemented std::fmt::Display for CqlValue instead.
After all the work related to moving CqlValueDisplayer to scylla-cql, we can finally mark CqlValue as non_exhaustive.
ae8cc53
to
7810380
Compare
@Lorak-mmk done, and CI passed |
Motivation
The motivation is quite obvious - we want to be able to extend
CqlValue
without introducing API breaking changes in the future.Obstacles
When you simply try to add
non_exhaustive
toCqlValue
, the compiler starts to complain. The reason is thatCqlValue
is defined inscylla-cql
crate, and we match against its instance inCqlValueDisplayer
(scylla crate). The compiler complains that_
arm is not covered - which makes sense.We could obviously add the
_
arm, but it's not ok. In the future, if someone adds new variant toCqlValue
, the compliler won't complain about uncovered variant inCqlValueDisplayer
. It will result in runtime error (or panic) when someone tries to display a CqlValue instance holding a new variant. So, TLDR, all commits but last are related to cleaning up, and movingpretty
module fromscylla
toscylla-cql
.Things to discuss
chrono 0.4 - Very important!!!
In current version of PR, chrono 0.4 dependency is no longer optional (and hidden behind
chrono-04
feature). It's needed unconditionally for some conversions used instd::fmt::Display
implementation forCqlValue
. I'm not sure how to address it. I don't see (or know of) any alternative solution to this...Pre-review checklist
[ ] I added relevant tests for new features and bug fixes.[ ] I have adjusted the documentation in./docs/source/
.[ ] I added appropriateFixes:
annotations to PR description.