use rudb_common::Value;
use rudb_plan::ExprRef;
use crate::binder::Binder;
const MICROS_PER_DAY: i64 = 86_400 * 1_000_000;
const USER: &str = "duckdb";
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub(crate) enum Context {
Instant,
LocalInstant,
Date,
ZonedTime,
LocalTime,
Database,
Schema,
User,
}
const KEYWORDS: &[(&str, Context)] = &[
("current_catalog", Context::Database),
("current_date", Context::Date),
("current_schema", Context::Schema),
("current_time", Context::ZonedTime),
("current_timestamp", Context::Instant),
("current_user", Context::User),
("localtime", Context::LocalTime),
("localtimestamp", Context::LocalInstant),
("session_user", Context::User),
("user", Context::User),
];
const CALLS: &[(&str, Context)] = &[
("current_catalog", Context::Database),
("current_database", Context::Database),
("current_date", Context::Date),
("current_localtime", Context::LocalTime),
("current_localtimestamp", Context::LocalInstant),
("current_schema", Context::Schema),
("current_user", Context::User),
("get_current_time", Context::ZonedTime),
("get_current_timestamp", Context::Instant),
("now", Context::Instant),
("session_user", Context::User),
("today", Context::Date),
("transaction_timestamp", Context::Instant),
("user", Context::User),
];
impl Binder<'_> {
pub(crate) fn context_keyword(&mut self, word: &str) -> Option<ExprRef> {
let (_, what) = KEYWORDS.iter().find(|(name, _)| name.eq_ignore_ascii_case(word))?;
Some(self.context(*what))
}
pub(crate) fn context_call(&mut self, name: &str) -> Option<ExprRef> {
let (_, what) = CALLS.iter().find(|(held, _)| held.eq_ignore_ascii_case(name))?;
Some(self.context(*what))
}
fn context(&mut self, what: Context) -> ExprRef {
let instant = self.instant();
let midnight = || instant.rem_euclid(MICROS_PER_DAY);
let value = match what {
Context::Instant => Value::TimestampTz(instant),
Context::LocalInstant => Value::Timestamp(instant),
Context::Date => {
let days = instant.div_euclid(MICROS_PER_DAY);
Value::Date(i32::try_from(days).unwrap_or(i32::MAX))
}
Context::ZonedTime => Value::TimeTz(midnight()),
Context::LocalTime => Value::Time(midnight()),
Context::Database => Value::Varchar(self.catalog().default_catalog().to_string()),
Context::Schema => Value::Varchar(self.catalog().default_schema().to_string()),
Context::User => Value::Varchar(USER.to_string()),
};
self.plan_mut().add_constant(value)
}
}
pub(crate) fn micros_now() -> i64 {
std::time::SystemTime::now()
.duration_since(std::time::UNIX_EPOCH)
.map_or(0, |since| i64::try_from(since.as_micros()).unwrap_or(i64::MAX))
}
#[cfg(test)]
mod tests {
use super::{CALLS, Context, KEYWORDS, MICROS_PER_DAY, micros_now};
#[test]
fn the_two_name_lists_are_sorted_and_have_no_repeats() {
for list in [KEYWORDS, CALLS] {
let names: Vec<&str> = list.iter().map(|(name, _)| *name).collect();
let mut sorted = names.clone();
sorted.sort_unstable();
sorted.dedup();
assert_eq!(names, sorted);
}
assert_eq!(KEYWORDS.len(), 10);
assert_eq!(CALLS.len(), 14);
}
#[test]
fn the_names_that_take_only_one_of_the_two_spellings_are_the_five_measured() {
let only_keyword: Vec<&str> = KEYWORDS
.iter()
.map(|(name, _)| *name)
.filter(|name| !CALLS.iter().any(|(held, _)| held == name))
.collect();
assert_eq!(
only_keyword,
vec!["current_time", "current_timestamp", "localtime", "localtimestamp"]
);
let only_call: Vec<&str> = CALLS
.iter()
.map(|(name, _)| *name)
.filter(|name| !KEYWORDS.iter().any(|(held, _)| held == name))
.collect();
assert_eq!(
only_call,
vec![
"current_database",
"current_localtime",
"current_localtimestamp",
"get_current_time",
"get_current_timestamp",
"now",
"today",
"transaction_timestamp"
]
);
}
#[test]
fn every_answer_has_a_name_that_asks_for_it() {
let wanted = [
Context::Instant,
Context::LocalInstant,
Context::Date,
Context::ZonedTime,
Context::LocalTime,
Context::Database,
Context::Schema,
Context::User,
];
for what in wanted {
assert!(
KEYWORDS.iter().chain(CALLS).any(|(_, held)| *held == what),
"nothing asks for {what:?}"
);
}
}
#[test]
fn the_clock_reads_forward_and_splits_into_a_date_and_a_time_within_the_day() {
assert!(micros_now() > 1_704_067_200_000_000);
for instant in [-MICROS_PER_DAY - 1, -1, 0, 1, MICROS_PER_DAY + 1, micros_now()] {
let time = instant.rem_euclid(MICROS_PER_DAY);
assert!((0..MICROS_PER_DAY).contains(&time), "{instant} split badly");
assert_eq!(instant.div_euclid(MICROS_PER_DAY) * MICROS_PER_DAY + time, instant);
}
}
}