use super::*;
use hawdb_core::sql_timestamp::{self, TimestampCast, TimestampType};
use hawdb_core::TimestampPrecision;
use hawdb_expression::sql::bind_sql_value;
fn kind(zoned: bool, precision: u8) -> SqlDataType {
SqlDataType::Timestamp(TimestampType {
zoned,
precision: TimestampPrecision::new(precision).unwrap(),
})
}
#[test]
fn timestamp_ddl_accepts_precision_and_time_zone_clauses() {
let SqlStatement::CreateTable(create) = parse_postgres_sql(
"CREATE TABLE t (a TIMESTAMP, b TIMESTAMP(0) WITHOUT TIME ZONE, \
c TIMESTAMP(3) WITH TIME ZONE, d TIMESTAMPTZ, e timestamptz(2))",
)
.unwrap() else {
panic!("CREATE TABLE")
};
assert_eq!(
create
.columns
.iter()
.map(|column| column.data_type)
.collect::<Vec<_>>(),
vec![
kind(false, 6),
kind(false, 0),
kind(true, 3),
kind(true, 6),
kind(true, 2)
]
);
for sql in [
"CREATE TABLE t (a TIMESTAMP(7))",
"CREATE TABLE t (a TIMESTAMP(-1))",
"CREATE TABLE t (a TIMESTAMPTZ WITHOUT TIME ZONE)",
"CREATE TABLE t (a TIMESTAMP WITH ZONE)",
] {
assert!(parse_postgres_sql(sql).is_err(), "{sql}");
}
}
#[test]
fn timestamp_literals_and_casts_lower_to_host_values() {
let SqlStatement::Insert(insert) = parse_postgres_sql(
"INSERT INTO t (a, b, c, d, e) VALUES (TIMESTAMP '2026-11-05 12:00:00', \
TIMESTAMP WITH TIME ZONE '2026-11-05 14:00:00+02', '2026-11-05'::timestamptz(0), \
CAST($1 AS TIMESTAMP WITHOUT TIME ZONE), CAST(TIMESTAMP '2026-11-05 12:00:00.5' AS TEXT))",
)
.unwrap() else {
panic!("INSERT")
};
let local =
sql_timestamp::local_value(sql_timestamp::parse_timestamp("2026-11-05 12:00:00").unwrap())
.unwrap();
let zoned = sql_timestamp::zoned_value(
sql_timestamp::parse_timestamptz("2026-11-05 12:00:00Z").unwrap(),
)
.unwrap();
let parameters = [Value::String("2026-11-05 12:00".into())];
let values = insert.rows[0]
.iter()
.map(|value| bind_sql_value(value, ¶meters).unwrap())
.collect::<Vec<_>>();
assert_eq!(values[0], local);
assert_eq!(values[1], zoned);
assert!(matches!(values[2], Value::DateTime(_)));
assert_eq!(values[3], local);
assert_eq!(values[4], Value::String("2026-11-05 12:00:00.5".into()));
let SqlStatement::Select(select) =
parse_postgres_sql("SELECT CAST(at AS TIMESTAMPTZ(3)) FROM t").unwrap()
else {
panic!("SELECT")
};
let SelectProjection::Expression { expression, .. } = &select.projection[0] else {
panic!("expression")
};
assert!(matches!(
expression.kind,
ExprKind::TimestampCast {
target: TimestampCast::Timestamp(TimestampType { zoned: true, .. }),
..
}
));
for sql in [
"SELECT TIMESTAMP '2026-11-05 12:00:00+02'",
"SELECT TIMESTAMPTZ WITH TIME ZONE '2026-11-05'",
"SELECT DATE '2026-11-05'",
"SELECT TIME '12:00'",
"SELECT CAST('x' AS BIGINT WITH TIME ZONE)",
"SELECT CAST('2026-11-05' AS TIMESTAMP(1, 2))",
"SELECT CAST('2026-11-05' AS TEXT(3))",
] {
assert!(parse_postgres_sql(sql).is_err(), "{sql}");
}
}
#[test]
fn clock_functions_lower_to_per_execution_constants_and_defaults() {
let SqlStatement::Select(select) = parse_postgres_sql(
"SELECT CURRENT_TIMESTAMP, current_timestamp(3), now(), LOCALTIMESTAMP(0), \"now\"",
)
.unwrap() else {
panic!("SELECT")
};
let kinds = select
.projection
.iter()
.map(|projection| {
let SelectProjection::Expression { expression, .. } = projection else {
panic!("expression")
};
match &expression.kind {
ExprKind::Value(SqlValue::Expression(inner)) => match inner.kind {
ExprKind::CurrentTimestamp(kind) => Some(kind),
_ => panic!("clock expression"),
},
ExprKind::Column(_) => None,
other => panic!("unexpected {other:?}"),
}
})
.collect::<Vec<_>>();
let clock = |zoned, precision| {
Some(TimestampType {
zoned,
precision: TimestampPrecision::new(precision).unwrap(),
})
};
assert_eq!(
kinds,
vec![
clock(true, 6),
clock(true, 3),
clock(true, 6),
clock(false, 0),
None
]
);
let SqlStatement::CreateTable(create) = parse_postgres_sql(
"CREATE TABLE t (a TIMESTAMPTZ DEFAULT CURRENT_TIMESTAMP(2), b TIMESTAMP DEFAULT LOCALTIMESTAMP, \
c TIMESTAMPTZ DEFAULT now())",
)
.unwrap() else {
panic!("CREATE TABLE")
};
let defaults = create
.columns
.iter()
.map(|column| column.default.clone())
.collect::<Vec<_>>();
assert_eq!(
defaults,
vec![
Some(SqlColumnDefault::CurrentTimestamp(clock(true, 2).unwrap())),
Some(SqlColumnDefault::CurrentTimestamp(clock(false, 6).unwrap())),
Some(SqlColumnDefault::CurrentTimestamp(clock(true, 6).unwrap())),
]
);
for sql in [
"SELECT CURRENT_TIMESTAMP(7)",
"SELECT CURRENT_TIMESTAMP()",
"SELECT now(1)",
"CREATE TABLE t (a TEXT DEFAULT CAST(now() AS TEXT))",
] {
assert!(parse_postgres_sql(sql).is_err(), "{sql}");
}
}