Skip to content

Commit

Permalink
scylla-cql: Improve support of common date and time types
Browse files Browse the repository at this point in the history
Adds support of 'time' and 'chrono' types for CQL queries.

Changelist:
- Rework scylla-cql 'Time', 'Date' and 'Timestamp' types to align with
  CQL data representation;
- Add 'Cql'- prefix to builtin date and time types;
- Implement conversion traits between chrono, time and Cql- types;
- Implement CQL 'Value' trait for 'chrono' and 'time' types;
- Implement 'FromCqlVal' trait for 'chrono' and 'time' types.
  • Loading branch information
Anfid committed Aug 23, 2023
1 parent 95e4aeb commit 00ef6d9
Show file tree
Hide file tree
Showing 15 changed files with 2,120 additions and 343 deletions.
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` <----> `u32`, `chrono::NaiveDate`, `time::Date`
* `Time` <----> `i64`, `chrono::NaiveTime`, `time::Time`
* `Timestamp` <----> `i64`, `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

0 comments on commit 00ef6d9

Please sign in to comment.