Skip to content

Commit

Permalink
Support RFC 3339 timestamp values in sql
Browse files Browse the repository at this point in the history
  • Loading branch information
joshua-spacetime committed Feb 10, 2025
1 parent 5159c79 commit 8820a65
Show file tree
Hide file tree
Showing 6 changed files with 51 additions and 5 deletions.
1 change: 1 addition & 0 deletions Cargo.lock

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

23 changes: 22 additions & 1 deletion crates/expr/src/check.rs
Original file line number Diff line number Diff line change
Expand Up @@ -239,6 +239,7 @@ mod tests {
(
"t",
ProductType::from([
("ts", AlgebraicType::timestamp()),
("i8", AlgebraicType::I8),
("u8", AlgebraicType::U8),
("i16", AlgebraicType::I16),
Expand Down Expand Up @@ -320,9 +321,29 @@ mod tests {
sql: "select * from t where u256 = 1e40",
msg: "u256",
},
TestCase {
sql: "select * from t where ts = '2025-02-10T15:45:30Z'",
msg: "timestamp",
},
TestCase {
sql: "select * from t where ts = '2025-02-10T15:45:30.123Z'",
msg: "timestamp ms",
},
TestCase {
sql: "select * from t where ts = '2025-02-10T15:45:30.123456789Z'",
msg: "timestamp ns",
},
TestCase {
sql: "select * from t where ts = '2025-02-10 15:45:30+02:00'",
msg: "timestamp with timezone",
},
TestCase {
sql: "select * from t where ts = '2025-02-10 15:45:30.123+02:00'",
msg: "timestamp ms with timezone",
},
] {
let result = parse_and_type_sub(sql, &tx);
assert!(result.is_ok(), "{msg}");
assert!(result.is_ok(), "name: {}, error: {}", msg, result.unwrap_err());
}
}

Expand Down
18 changes: 14 additions & 4 deletions crates/expr/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,11 @@ use errors::{DuplicateName, InvalidLiteral, InvalidOp, InvalidWildcard, Unexpect
use ethnum::i256;
use ethnum::u256;
use expr::{Expr, FieldProject, ProjectList, ProjectName, RelExpr};
use spacetimedb_lib::ser::Serialize;
use spacetimedb_lib::timestamp::parse_timestamp_from_str;
use spacetimedb_lib::{from_hex_pad, AlgebraicType, AlgebraicValue, ConnectionId, Identity};
use spacetimedb_sats::algebraic_type::fmt::fmt_algebraic_type;
use spacetimedb_sats::algebraic_value::ser::ValueSerializer;
use spacetimedb_schema::schema::ColumnSchema;
use spacetimedb_sql_parser::ast::{self, BinOp, ProjectElem, SqlExpr, SqlIdent, SqlLiteral};

Expand Down Expand Up @@ -62,10 +65,10 @@ pub(crate) fn type_expr(vars: &Relvars, expr: SqlExpr, expected: Option<&Algebra
match (expr, expected) {
(SqlExpr::Lit(SqlLiteral::Bool(v)), None | Some(AlgebraicType::Bool)) => Ok(Expr::bool(v)),
(SqlExpr::Lit(SqlLiteral::Bool(_)), Some(ty)) => Err(UnexpectedType::new(&AlgebraicType::Bool, ty).into()),
(SqlExpr::Lit(SqlLiteral::Str(v)), None | Some(AlgebraicType::String)) => Ok(Expr::str(v)),
(SqlExpr::Lit(SqlLiteral::Str(_)), Some(ty)) => Err(UnexpectedType::new(&AlgebraicType::String, ty).into()),
(SqlExpr::Lit(SqlLiteral::Num(_) | SqlLiteral::Hex(_)), None) => Err(Unresolved::Literal.into()),
(SqlExpr::Lit(SqlLiteral::Num(v) | SqlLiteral::Hex(v)), Some(ty)) => Ok(Expr::Value(
(SqlExpr::Lit(SqlLiteral::Str(_) | SqlLiteral::Num(_) | SqlLiteral::Hex(_)), None) => {
Err(Unresolved::Literal.into())
}
(SqlExpr::Lit(SqlLiteral::Str(v) | SqlLiteral::Num(v) | SqlLiteral::Hex(v)), Some(ty)) => Ok(Expr::Value(
parse(&v, ty).map_err(|_| InvalidLiteral::new(v.into_string(), ty))?,
ty.clone(),
)),
Expand Down Expand Up @@ -124,6 +127,7 @@ fn op_supports_type(_op: BinOp, t: &AlgebraicType) -> bool {
|| t.is_bytes()
|| t.is_identity()
|| t.is_connection_id()
|| t.is_timestamp()
}

/// Parse an integer literal into an [AlgebraicValue]
Expand Down Expand Up @@ -171,6 +175,11 @@ where

/// Parses a source text literal as a particular type
pub(crate) fn parse(value: &str, ty: &AlgebraicType) -> anyhow::Result<AlgebraicValue> {
let to_timestamp = || {
parse_timestamp_from_str(value)?
.serialize(ValueSerializer)
.with_context(|| "Could not parse timestamp")
};
let to_bytes = || {
from_hex_pad::<Vec<u8>, _>(value)
.map(|v| v.into_boxed_slice())
Expand Down Expand Up @@ -303,6 +312,7 @@ pub(crate) fn parse(value: &str, ty: &AlgebraicType) -> anyhow::Result<Algebraic
AlgebraicValue::U256,
),
AlgebraicType::String => Ok(AlgebraicValue::String(value.into())),
t if t.is_timestamp() => to_timestamp(),
t if t.is_bytes() => to_bytes(),
t if t.is_identity() => to_identity(),
t if t.is_connection_id() => to_connection_id(),
Expand Down
1 change: 1 addition & 0 deletions crates/lib/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ spacetimedb-data-structures.workspace = true

anyhow.workspace = true
bitflags.workspace = true
chrono.workspace = true
derive_more.workspace = true
enum-as-inner.workspace = true
hex.workspace = true
Expand Down
1 change: 1 addition & 0 deletions crates/lib/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ pub mod query;
pub mod relation;
pub mod scheduler;
pub mod st_var;
pub mod timestamp;
pub mod version;

pub mod type_def {
Expand Down
12 changes: 12 additions & 0 deletions crates/lib/src/timestamp.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
use anyhow::Context;
use chrono::DateTime;
use spacetimedb_sats::timestamp::Timestamp;

/// Parses an RFC 3339 formated timestamp string
pub fn parse_timestamp_from_str(str: &str) -> anyhow::Result<Timestamp> {
DateTime::parse_from_rfc3339(str)
.map_err(|err| anyhow::anyhow!(err))
.with_context(|| "Invalid timestamp format. Expected RFC 3339 format (e.g. '2025-02-10 15:45:30').")
.map(|dt| dt.timestamp_micros())
.map(Timestamp::from_micros_since_unix_epoch)
}

0 comments on commit 8820a65

Please sign in to comment.