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

Improve common time and date crates support #745

Merged
merged 7 commits into from
Nov 10, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions .github/workflows/cassandra.yml
Original file line number Diff line number Diff line change
Expand Up @@ -26,10 +26,10 @@ jobs:
docker compose -f test/cluster/cassandra/docker-compose.yml up -d --wait
# A separate step for building to separate measuring time of compilation and testing
- name: Build the project
run: cargo build --verbose --tests
run: cargo build --verbose --tests --features "full-serialization"
- name: Run tests on cassandra
run: |
CDC='disabled' SCYLLA_URI=172.42.0.2:9042 SCYLLA_URI2=172.42.0.3:9042 SCYLLA_URI3=172.42.0.4:9042 cargo test --verbose -- --skip test_views_in_schema_info --skip test_large_batch_statements
CDC='disabled' SCYLLA_URI=172.42.0.2:9042 SCYLLA_URI2=172.42.0.3:9042 SCYLLA_URI3=172.42.0.4:9042 cargo test --verbose --features "full-serialization" -- --skip test_views_in_schema_info --skip test_large_batch_statements
- name: Stop the cluster
if: ${{ always() }}
run: docker compose -f test/cluster/cassandra/docker-compose.yml stop
Expand Down
12 changes: 6 additions & 6 deletions .github/workflows/rust.yml
Original file line number Diff line number Diff line change
Expand Up @@ -32,14 +32,14 @@ jobs:
run: cargo clippy --verbose --all-targets -- -Aclippy::uninlined_format_args
- name: Cargo check without features
run: cargo check --all-targets --manifest-path "scylla/Cargo.toml" --features ""
- name: Cargo check with secrecy feature
run: cargo check --all-targets --manifest-path "scylla/Cargo.toml" --features "secret"
- name: Cargo check with all serialization features
run: cargo check --all-targets --manifest-path "scylla/Cargo.toml" --features "full-serialization"
- name: Build scylla-cql
run: cargo build --verbose --all-targets --manifest-path "scylla-cql/Cargo.toml"
run: cargo build --verbose --all-targets --manifest-path "scylla-cql/Cargo.toml" --features "full-serialization"
- name: Build
run: cargo build --verbose --all-targets
run: cargo build --verbose --all-targets --features "full-serialization"
- name: Run tests
run: SCYLLA_URI=172.42.0.2:9042 SCYLLA_URI2=172.42.0.3:9042 SCYLLA_URI3=172.42.0.4:9042 cargo test --verbose
run: SCYLLA_URI=172.42.0.2:9042 SCYLLA_URI2=172.42.0.3:9042 SCYLLA_URI3=172.42.0.4:9042 cargo test --verbose --features "full-serialization"
- name: Stop the cluster
if: ${{ always() }}
run: docker compose -f test/cluster/docker-compose.yml stop
Expand All @@ -63,7 +63,7 @@ jobs:
- name: Use MSRV Cargo.lock
run: mv Cargo.lock.msrv Cargo.lock
- name: MSRV cargo check with features
run: cargo check --verbose --all-targets --locked
run: cargo check --verbose --all-targets --all-features --locked
- name: MSRV cargo check without features
run: cargo check --verbose --all-targets --locked --manifest-path "scylla/Cargo.toml"
- name: MSRV cargo check scylla-cql
Expand Down
29 changes: 29 additions & 0 deletions Cargo.lock.msrv

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

8 changes: 4 additions & 4 deletions docs/source/data-types/data-types.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,9 +21,9 @@ Database types and their Rust equivalents:
* `Blob` <----> `Vec<u8>`
* `Inet` <----> `std::net::IpAddr`
* `Uuid`, `Timeuuid` <----> `uuid::Uuid`
* `Date` <----> `chrono::NaiveDate`, `u32`
* `Time` <----> `chrono::Duration`
* `Timestamp` <----> `chrono::Duration`
* `Date` <----> `value::CqlDate`, `chrono::NaiveDate`, `time::Date`
* `Time` <----> `value::CqlTime`, `chrono::NaiveTime`, `time::Time`
* `Timestamp` <----> `value::CqlTimestamp`, `chrono::DateTime<Utc>`, `time::OffsetDateTime`
* `Duration` <----> `value::CqlDuration`
* `Decimal` <----> `bigdecimal::Decimal`
* `Varint` <----> `num_bigint::BigInt`
Expand Down Expand Up @@ -55,4 +55,4 @@ Database types and their Rust equivalents:
tuple
udt

```
```
102 changes: 79 additions & 23 deletions docs/source/data-types/date.md
Original file line number Diff line number Diff line change
@@ -1,29 +1,77 @@
# Date

For most use cases `Date` can be represented as
[`chrono::NaiveDate`](https://docs.rs/chrono/0.4.19/chrono/naive/struct.NaiveDate.html).\
`NaiveDate` supports dates from -262145-1-1 to 262143-12-31.
Depending on feature flags, three different types can be used to interact with date.

For dates outside of this range you can use the raw `u32` representation.
Internally [date](https://docs.scylladb.com/stable/cql/types.html#dates) is represented as number of days since
-5877641-06-23 i.e. 2^31 days before unix epoch.

## CqlDate

Without any extra features enabled, only `frame::value::CqlDate` is available. It's an
[`u32`](https://doc.rust-lang.org/std/primitive.u32.html) wrapper and it matches the internal date representation.

However, for most use cases other types are more practical. See following sections for `chrono` and `time`.

### Using `chrono::NaiveDate`:
```rust
# extern crate scylla;
# use scylla::Session;
# use std::error::Error;
# async fn check_only_compiles(session: &Session) -> Result<(), Box<dyn Error>> {
use scylla::frame::value::CqlDate;
use scylla::IntoTypedRows;

// 1970-01-08
let to_insert = CqlDate((1 << 31) + 7);

// Insert date into the table
session
.query("INSERT INTO keyspace.table (a) VALUES(?)", (to_insert,))
.await?;

// Read raw Date from the table
if let Some(rows) = session
.query("SELECT a FROM keyspace.table", &[])
.await?
.rows
{
for row in rows.into_typed::<(CqlDate,)>() {
let (date_value,): (CqlDate,) = row?;
}
}
# Ok(())
# }
```

## chrono::NaiveDate

If full range is not required and `chrono` feature is enabled,
[`chrono::NaiveDate`](https://docs.rs/chrono/0.4/chrono/naive/struct.NaiveDate.html) can be used.
[`chrono::NaiveDate`](https://docs.rs/chrono/0.4/chrono/naive/struct.NaiveDate.html) supports dates from
-262145-01-01 to 262143-12-31.

```rust
# extern crate chrono;
# extern crate scylla;
# use scylla::Session;
# use std::error::Error;
# async fn check_only_compiles(session: &Session) -> Result<(), Box<dyn Error>> {
use chrono::NaiveDate;
use scylla::IntoTypedRows;
use chrono::naive::NaiveDate;

// Insert some date into the table
let to_insert: NaiveDate = NaiveDate::from_ymd_opt(2021, 3, 24).unwrap();
// 2021-03-24
let to_insert = NaiveDate::from_ymd_opt(2021, 3, 24).unwrap();

// Insert date into the table
session
.query("INSERT INTO keyspace.table (a) VALUES(?)", (to_insert,))
.await?;

// Read NaiveDate from the table
if let Some(rows) = session.query("SELECT a FROM keyspace.table", &[]).await?.rows {
if let Some(rows) = session
.query("SELECT a FROM keyspace.table", &[])
.await?
.rows
{
for row in rows.into_typed::<(NaiveDate,)>() {
let (date_value,): (NaiveDate,) = row?;
}
Expand All @@ -32,32 +80,40 @@ if let Some(rows) = session.query("SELECT a FROM keyspace.table", &[]).await?.ro
# }
```

### Using raw `u32` representation
Internally `Date` is represented as number of days since -5877641-06-23 i.e. 2^31 days before unix epoch.
## time::Date

Alternatively, `time` feature can be used to enable support of
[`time::Date`](https://docs.rs/time/0.3/time/struct.Date.html).
[`time::Date`](https://docs.rs/time/0.3/time/struct.Date.html)'s value range depends on feature flags, see its
documentation to get more info.

```rust
# extern crate scylla;
# extern crate time;
# use scylla::Session;
# use std::error::Error;
# async fn check_only_compiles(session: &Session) -> Result<(), Box<dyn Error>> {
use scylla::frame::value::Date;
use scylla::frame::response::result::CqlValue;
use scylla::IntoTypedRows;
use time::{Date, Month};

// Insert date using raw u32 representation
let to_insert: Date = Date(2_u32.pow(31)); // 1970-01-01
// 2021-03-24
let to_insert = Date::from_calendar_date(2021, Month::March, 24).unwrap();

// Insert date into the table
session
.query("INSERT INTO keyspace.table (a) VALUES(?)", (to_insert,))
.await?;

// Read raw Date from the table
if let Some(rows) = session.query("SELECT a FROM keyspace.table", &[]).await?.rows {
for row in rows {
let date_value: u32 = match row.columns[0] {
Some(CqlValue::Date(date_value)) => date_value,
_ => panic!("Should be a date!")
};
// Read Date from the table
if let Some(rows) = session
.query("SELECT a FROM keyspace.table", &[])
.await?
.rows
{
for row in rows.into_typed::<(Date,)>() {
let (date_value,): (Date,) = row?;
}
}
# Ok(())
# }
```
```
112 changes: 98 additions & 14 deletions docs/source/data-types/time.md
Original file line number Diff line number Diff line change
@@ -1,33 +1,117 @@
# Time
`Time` is represented as [`chrono::Duration`](https://docs.rs/chrono/0.4.19/chrono/struct.Duration.html)

Internally `Time` is represented as number of nanoseconds since midnight.
It can't be negative or exceed `86399999999999` (24 hours).
Depending on feature flags used, three different types can be used to interact with time.

When sending in a query it needs to be wrapped in `value::Time` to differentiate from [`Timestamp`](timestamp.md)
Internally [time](https://docs.scylladb.com/stable/cql/types.html#times) is represented as number of nanoseconds since
midnight. It can't be negative or exceed `86399999999999` (23:59:59.999999999).

## CqlTime

Without any extra features enabled, only `frame::value::CqlTime` is available. It's an
[`i64`](https://doc.rust-lang.org/std/primitive.i64.html) wrapper and it matches the internal time representation.

However, for most use cases other types are more practical. See following sections for `chrono` and `time`.

```rust
# extern crate scylla;
# use scylla::Session;
# use std::error::Error;
# async fn check_only_compiles(session: &Session) -> Result<(), Box<dyn Error>> {
use scylla::frame::value::CqlTime;
use scylla::IntoTypedRows;

// 64 seconds since midnight
let to_insert = CqlTime(64 * 1_000_000_000);

// Insert time into the table
session
.query("INSERT INTO keyspace.table (a) VALUES(?)", (to_insert,))
.await?;

// Read time from the table
if let Some(rows) = session
.query("SELECT a FROM keyspace.table", &[])
.await?
.rows
{
for row in rows.into_typed::<(CqlTime,)>() {
let (time_value,): (CqlTime,) = row?;
}
}
# Ok(())
# }
```

## chrono::NaiveTime

If `chrono` feature is enabled, [`chrono::NaiveTime`](https://docs.rs/chrono/0.4/chrono/naive/struct.NaiveDate.html)
can be used to interact with the database. Although chrono can represent leap seconds, they are not supported.
Attempts to convert [`chrono::NaiveTime`](https://docs.rs/chrono/0.4/chrono/naive/struct.NaiveDate.html) with leap
second to `CqlTime` or write it to the database will return an error.

```rust
# extern crate chrono;
# extern crate scylla;
# use scylla::Session;
# use std::error::Error;
# async fn check_only_compiles(session: &Session) -> Result<(), Box<dyn Error>> {
use chrono::NaiveTime;
use scylla::IntoTypedRows;

// 01:02:03.456,789,012
let to_insert = NaiveTime::from_hms_nano_opt(1, 2, 3, 456_789_012);

// Insert time into the table
session
.query("INSERT INTO keyspace.table (a) VALUES(?)", (to_insert,))
.await?;

// Read time from the table
if let Some(rows) = session
.query("SELECT a FROM keyspace.table", &[])
.await?
.rows
{
for row in rows.into_typed::<(NaiveTime,)>() {
let (time_value,): (NaiveTime,) = row?;
}
}
# Ok(())
# }
```

## time::Time

If `time` feature is enabled, [`time::Time`](https://docs.rs/time/0.3/time/struct.Time.html) can be used to interact
with the database.

```rust
# extern crate scylla;
# extern crate time;
# use scylla::Session;
# use std::error::Error;
# async fn check_only_compiles(session: &Session) -> Result<(), Box<dyn Error>> {
use scylla::IntoTypedRows;
use scylla::frame::value::Time;
use chrono::Duration;
use time::Time;

// 01:02:03.456,789,012
let to_insert = Time::from_hms_nano(1, 2, 3, 456_789_012).unwrap();

// Insert some time into the table
let to_insert: Duration = Duration::seconds(64);
// Insert time into the table
session
.query("INSERT INTO keyspace.table (a) VALUES(?)", (Time(to_insert),))
.query("INSERT INTO keyspace.table (a) VALUES(?)", (to_insert,))
.await?;

// Read time from the table, no need for a wrapper here
if let Some(rows) = session.query("SELECT a FROM keyspace.table", &[]).await?.rows {
for row in rows.into_typed::<(Duration,)>() {
let (time_value,): (Duration,) = row?;
// Read time from the table
if let Some(rows) = session
.query("SELECT a FROM keyspace.table", &[])
.await?
.rows
{
for row in rows.into_typed::<(Time,)>() {
let (time_value,): (Time,) = row?;
}
}
# Ok(())
# }
```
```
Loading