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

expr: Add protobuf for chrono types #11801

Merged
merged 1 commit into from
Apr 15, 2022
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
2 changes: 2 additions & 0 deletions src/repr/build.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,11 +11,13 @@ fn main() {
prost_build::Config::new()
.compile_protos(
&[
"chrono.proto",
"row.proto",
"strconv.proto",
"relation_and_scalar.proto",
"adt/array.proto",
"adt/char.proto",
"adt/datetime.proto",
"adt/numeric.proto",
"adt/varchar.proto",
],
Expand Down
17 changes: 15 additions & 2 deletions src/repr/src/adt/datetime.rs
Original file line number Diff line number Diff line change
Expand Up @@ -24,13 +24,23 @@ use crate::adt::interval::Interval;

use mz_lowertest::MzReflect;

// The `Arbitrary` impls are only used during testing and we gate them
// behind `cfg(feature = "test-utils")`, so `proptest` can remain a dev-dependency.
// See https://github.com/MaterializeInc/materialize/pull/11717.
#[cfg(feature = "test-utils")]
use {
crate::chrono::any_fixed_offset, crate::chrono::any_timezone, proptest::prelude::*,
proptest_derive::Arbitrary,
};

/// Units of measurements associated with dates and times.
///
/// TODO(benesch): with enough thinking, this type could probably be merged with
/// `DateTimeField`.
#[derive(
Clone, Copy, Debug, PartialOrd, Ord, PartialEq, Eq, Hash, Serialize, Deserialize, MzReflect,
)]
#[cfg_attr(feature = "test-utils", derive(Arbitrary))]
pub enum DateTimeUnits {
Epoch,
Millennium,
Expand Down Expand Up @@ -326,10 +336,13 @@ impl DateTimeFieldValue {

/// Parsed timezone.
#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash, Serialize, Deserialize, MzReflect)]
#[cfg_attr(feature = "test-utils", derive(Arbitrary))]
pub enum Timezone {
#[serde(with = "fixed_offset_serde")]
FixedOffset(FixedOffset),
Tz(Tz),
FixedOffset(
#[cfg_attr(feature = "test-utils", proptest(strategy = "any_fixed_offset()"))] FixedOffset,
),
Tz(#[cfg_attr(feature = "test-utils", proptest(strategy = "any_timezone()"))] Tz),
}

// We need to implement Serialize and Deserialize traits to include Timezone in the UnaryFunc enum.
Expand Down
39 changes: 39 additions & 0 deletions src/repr/src/chrono.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
// Copyright Materialize, Inc. and contributors. All rights reserved.
//
// Use of this software is governed by the Business Source License
// included in the LICENSE file.
//
// As of the Change Date specified in that file, in accordance with
// the Business Source License, use of this software will be governed
// by the Apache License, Version 2.0.

#![cfg(feature = "test-utils")]

//! Custom [`proptest::strategy::Strategy`] implementations for the
//! [`chrono`] fields used inthe codebase.
//!
//! See the [`proptest`] docs[^1] for an example.
//!
//! [^1]: <https://altsysrq.github.io/proptest-book/proptest-derive/modifiers.html#strategy>

use chrono::{DateTime, Duration, FixedOffset, NaiveDateTime, Timelike, Utc};
use chrono_tz::{Tz, TZ_VARIANTS};
use proptest::prelude::Strategy;

pub fn any_naive_datetime() -> impl Strategy<Value = NaiveDateTime> {
use ::chrono::naive::{MAX_DATETIME, MIN_DATETIME};
(0..(MAX_DATETIME.nanosecond() - MIN_DATETIME.nanosecond()))
.prop_map(|x| MIN_DATETIME + Duration::nanoseconds(x as i64))
}

pub fn any_datetime() -> impl Strategy<Value = DateTime<Utc>> {
any_naive_datetime().prop_map(|x| DateTime::from_utc(x, Utc))
}

pub fn any_fixed_offset() -> impl Strategy<Value = FixedOffset> {
(-86_401..86_400).prop_map(FixedOffset::east)
}

pub fn any_timezone() -> impl Strategy<Value = Tz> {
(0..TZ_VARIANTS.len()).prop_map(|idx| *TZ_VARIANTS.get(idx).unwrap())
}
3 changes: 2 additions & 1 deletion src/repr/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -28,11 +28,12 @@ mod row;
mod scalar;

pub mod adt;
pub mod chrono;
pub mod proto;
pub mod strconv;
pub mod util;

pub use datum_vec::{DatumVec, DatumVecBorrow};
pub mod proto;
pub use relation::{ColumnName, ColumnType, NotNullViolation, RelationDesc, RelationType};
pub use row::{
datum_list_size, datum_size, datums_size, row_size, DatumList, DatumMap, Row, RowArena,
Expand Down
48 changes: 48 additions & 0 deletions src/repr/src/proto/adt/datetime.proto
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
// Copyright Materialize, Inc. and contributors. All rights reserved.
//
// Use of this software is governed by the Business Source License
// included in the LICENSE file.
//
// As of the Change Date specified in that file, in accordance with
// the Business Source License, use of this software will be governed
// by the Apache License, Version 2.0.

syntax = "proto3";

package adt.datetime;

import "chrono.proto";
import "google/protobuf/empty.proto";

message ProtoTimezone {
oneof kind {
chrono.ProtoFixedOffset fixed_offset = 1;
chrono.ProtoTz tz = 2;
}
}

message ProtoDateTimeUnits {
oneof kind {
google.protobuf.Empty epoch = 1;
google.protobuf.Empty millennium = 2;
google.protobuf.Empty century = 3;
google.protobuf.Empty decade = 4;
google.protobuf.Empty year = 5;
google.protobuf.Empty quarter = 6;
google.protobuf.Empty week = 7;
google.protobuf.Empty month = 8;
google.protobuf.Empty hour = 9;
google.protobuf.Empty day = 10;
google.protobuf.Empty day_of_week = 11;
google.protobuf.Empty day_of_year = 12;
google.protobuf.Empty iso_day_of_week = 13;
google.protobuf.Empty iso_day_of_year = 14;
google.protobuf.Empty minute = 15;
google.protobuf.Empty second = 16;
google.protobuf.Empty milliseconds = 17;
google.protobuf.Empty microseconds = 18;
google.protobuf.Empty timezone = 19;
google.protobuf.Empty timezone_hour = 20;
google.protobuf.Empty timezone_minute = 21;
}
}
126 changes: 126 additions & 0 deletions src/repr/src/proto/adt/datetime.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,126 @@
// Copyright Materialize, Inc. and contributors. All rights reserved.
//
// Use of this software is governed by the Business Source License
// included in the LICENSE file.
//
// As of the Change Date specified in that file, in accordance with
// the Business Source License, use of this software will be governed
// by the Apache License, Version 2.0.

//! Protobuf structs mirroring [`crate::adt::datetime`].

include!(concat!(env!("OUT_DIR"), "/adt.datetime.rs"));

use super::super::{ProtoRepr, TryFromProtoError};
use crate::adt::datetime::Timezone::*;
use crate::adt::datetime::{DateTimeUnits, Timezone};
use chrono::FixedOffset;
use chrono_tz::Tz;

impl From<&Timezone> for ProtoTimezone {
fn from(value: &Timezone) -> Self {
use proto_timezone::Kind;
ProtoTimezone {
kind: Some(match value {
FixedOffset(fo) => Kind::FixedOffset(fo.into_proto()),
Tz(tz) => Kind::Tz(tz.into_proto()),
}),
}
}
}

impl TryFrom<ProtoTimezone> for Timezone {
type Error = TryFromProtoError;

fn try_from(value: ProtoTimezone) -> Result<Self, Self::Error> {
use proto_timezone::Kind;
let kind = value
.kind
.ok_or_else(|| TryFromProtoError::MissingField("ProtoTimezone::kind".into()))?;
Ok(match kind {
Kind::FixedOffset(pof) => Timezone::FixedOffset(FixedOffset::from_proto(pof)?),
Kind::Tz(ptz) => Timezone::Tz(Tz::from_proto(ptz)?),
})
}
}

impl From<&DateTimeUnits> for ProtoDateTimeUnits {
fn from(value: &DateTimeUnits) -> Self {
use proto_date_time_units::Kind;
ProtoDateTimeUnits {
kind: Some(match value {
DateTimeUnits::Epoch => Kind::Epoch(()),
DateTimeUnits::Millennium => Kind::Millennium(()),
DateTimeUnits::Century => Kind::Century(()),
DateTimeUnits::Decade => Kind::Decade(()),
DateTimeUnits::Year => Kind::Year(()),
DateTimeUnits::Quarter => Kind::Quarter(()),
DateTimeUnits::Week => Kind::Week(()),
DateTimeUnits::Month => Kind::Month(()),
DateTimeUnits::Hour => Kind::Hour(()),
DateTimeUnits::Day => Kind::Day(()),
DateTimeUnits::DayOfWeek => Kind::DayOfWeek(()),
DateTimeUnits::DayOfYear => Kind::DayOfYear(()),
DateTimeUnits::IsoDayOfWeek => Kind::IsoDayOfWeek(()),
DateTimeUnits::IsoDayOfYear => Kind::IsoDayOfYear(()),
DateTimeUnits::Minute => Kind::Minute(()),
DateTimeUnits::Second => Kind::Second(()),
DateTimeUnits::Milliseconds => Kind::Milliseconds(()),
DateTimeUnits::Microseconds => Kind::Microseconds(()),
DateTimeUnits::Timezone => Kind::Timezone(()),
DateTimeUnits::TimezoneHour => Kind::TimezoneHour(()),
DateTimeUnits::TimezoneMinute => Kind::TimezoneMinute(()),
}),
}
}
}

impl TryFrom<ProtoDateTimeUnits> for DateTimeUnits {
type Error = TryFromProtoError;

fn try_from(value: ProtoDateTimeUnits) -> Result<Self, Self::Error> {
use proto_date_time_units::Kind;
let kind = value
.kind
.ok_or_else(|| TryFromProtoError::MissingField("ProtoDateTimeUnits.kind".into()))?;
Ok(match kind {
Kind::Epoch(_) => DateTimeUnits::Epoch,
Kind::Millennium(_) => DateTimeUnits::Millennium,
Kind::Century(_) => DateTimeUnits::Century,
Kind::Decade(_) => DateTimeUnits::Decade,
Kind::Year(_) => DateTimeUnits::Year,
Kind::Quarter(_) => DateTimeUnits::Quarter,
Kind::Week(_) => DateTimeUnits::Week,
Kind::Month(_) => DateTimeUnits::Month,
Kind::Hour(_) => DateTimeUnits::Hour,
Kind::Day(_) => DateTimeUnits::Day,
Kind::DayOfWeek(_) => DateTimeUnits::DayOfWeek,
Kind::DayOfYear(_) => DateTimeUnits::DayOfYear,
Kind::IsoDayOfWeek(_) => DateTimeUnits::IsoDayOfWeek,
Kind::IsoDayOfYear(_) => DateTimeUnits::IsoDayOfYear,
Kind::Minute(_) => DateTimeUnits::Minute,
Kind::Second(_) => DateTimeUnits::Second,
Kind::Milliseconds(_) => DateTimeUnits::Milliseconds,
Kind::Microseconds(_) => DateTimeUnits::Microseconds,
Kind::Timezone(_) => DateTimeUnits::Timezone,
Kind::TimezoneHour(_) => DateTimeUnits::TimezoneHour,
Kind::TimezoneMinute(_) => DateTimeUnits::TimezoneMinute,
})
}
}

#[cfg(test)]
mod tests {
use super::*;
use crate::proto::protobuf_roundtrip;
use proptest::prelude::*;

proptest! {
#[test]
fn datetimeunits_serialization_roundtrip(expect in any::<DateTimeUnits>() ) {
let actual = protobuf_roundtrip::<_, ProtoDateTimeUnits>(&expect);
assert!(actual.is_ok());
assert_eq!(actual.unwrap(), expect);
}
}
}
1 change: 1 addition & 0 deletions src/repr/src/proto/adt/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,5 +11,6 @@

pub mod array;
pub mod char;
pub mod datetime;
pub mod numeric;
pub mod varchar;
35 changes: 35 additions & 0 deletions src/repr/src/proto/chrono.proto
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
// Copyright Materialize, Inc. and contributors. All rights reserved.
//
// Use of this software is governed by the Business Source License
// included in the LICENSE file.
//
// As of the Change Date specified in that file, in accordance with
// the Business Source License, use of this software will be governed
// by the Apache License, Version 2.0.

syntax = "proto3";

package chrono;

message ProtoTz {
string name = 1;
}

message ProtoNaiveDate {
int32 year = 1;
uint32 ordinal = 2;
}

message ProtoNaiveTime {
uint32 secs = 1;
uint32 frac = 2;
}

message ProtoNaiveDateTime {
ProtoNaiveDate date = 1;
ProtoNaiveTime time = 2;
}

message ProtoFixedOffset {
int32 local_minus_utc = 1;
}
Loading