feat: support set timezone in db (#2992)

* feat: support set timezone in db

* chore: fix  ci

* chore: fix code advice

* fix: rename `time_zone` to `timezone`
This commit is contained in:
WU Jingdi
2023-12-27 17:19:39 +08:00
committed by GitHub
parent feec4e289d
commit d1ee1ba56a
30 changed files with 269 additions and 197 deletions

2
Cargo.lock generated
View File

@@ -1533,6 +1533,7 @@ dependencies = [
"common-recordbatch",
"common-telemetry",
"common-test-util",
"common-time",
"common-version",
"config",
"datanode",
@@ -1987,6 +1988,7 @@ dependencies = [
"chrono-tz 0.8.4",
"common-error",
"common-macro",
"once_cell",
"rand",
"serde",
"serde_json",

View File

@@ -1,5 +1,7 @@
# Node running mode, see `standalone.example.toml`.
mode = "distributed"
# The default timezone of the server
# default_timezone = "UTC"
[heartbeat]
# Interval for sending heartbeat task to the Metasrv, 5 seconds by default.

View File

@@ -2,6 +2,8 @@
mode = "standalone"
# Whether to enable greptimedb telemetry, true by default.
enable_telemetry = true
# The default timezone of the server
# default_timezone = "UTC"
# HTTP server options.
[http]

View File

@@ -32,6 +32,7 @@ common-recordbatch.workspace = true
common-telemetry = { workspace = true, features = [
"deadlock_detection",
] }
common-time.workspace = true
config = "0.13"
datanode.workspace = true
datatypes.workspace = true

View File

@@ -43,6 +43,12 @@ pub enum Error {
source: common_meta::error::Error,
},
#[snafu(display("Failed to init default timezone"))]
InitTimezone {
location: Location,
source: common_time::error::Error,
},
#[snafu(display("Failed to start procedure manager"))]
StartProcedureManager {
location: Location,
@@ -268,6 +274,7 @@ impl ErrorExt for Error {
| Error::LoadLayeredConfig { .. }
| Error::IllegalConfig { .. }
| Error::InvalidReplCommand { .. }
| Error::InitTimezone { .. }
| Error::ConnectEtcd { .. }
| Error::NotDataFromOutput { .. }
| Error::CreateDir { .. }

View File

@@ -22,6 +22,7 @@ use client::client_manager::DatanodeClients;
use common_meta::heartbeat::handler::parse_mailbox_message::ParseMailboxMessageHandler;
use common_meta::heartbeat::handler::HandlerGroupExecutor;
use common_telemetry::logging;
use common_time::timezone::set_default_timezone;
use frontend::frontend::FrontendOptions;
use frontend::heartbeat::handler::invalidate_table_cache::InvalidateTableCacheHandler;
use frontend::heartbeat::HeartbeatTask;
@@ -32,7 +33,7 @@ use servers::tls::{TlsMode, TlsOption};
use servers::Mode;
use snafu::{OptionExt, ResultExt};
use crate::error::{self, MissingConfigSnafu, Result, StartFrontendSnafu};
use crate::error::{self, InitTimezoneSnafu, MissingConfigSnafu, Result, StartFrontendSnafu};
use crate::options::{CliOptions, Options};
use crate::App;
@@ -217,6 +218,8 @@ impl StartCommand {
logging::info!("Frontend start command: {:#?}", self);
logging::info!("Frontend options: {:#?}", opts);
set_default_timezone(opts.default_timezone.as_deref()).context(InitTimezoneSnafu)?;
let meta_client_options = opts.meta_client.as_ref().context(MissingConfigSnafu {
msg: "'meta_client'",
})?;

View File

@@ -32,6 +32,7 @@ use common_meta::wal::{WalOptionsAllocator, WalOptionsAllocatorRef};
use common_procedure::ProcedureManagerRef;
use common_telemetry::info;
use common_telemetry::logging::LoggingOptions;
use common_time::timezone::set_default_timezone;
use datanode::config::{DatanodeOptions, ProcedureConfig, RegionEngineConfig, StorageConfig};
use datanode::datanode::{Datanode, DatanodeBuilder};
use file_engine::config::EngineConfig as FileEngineConfig;
@@ -51,8 +52,8 @@ use servers::Mode;
use snafu::ResultExt;
use crate::error::{
CreateDirSnafu, IllegalConfigSnafu, InitDdlManagerSnafu, InitMetadataSnafu, Result,
ShutdownDatanodeSnafu, ShutdownFrontendSnafu, StartDatanodeSnafu, StartFrontendSnafu,
CreateDirSnafu, IllegalConfigSnafu, InitDdlManagerSnafu, InitMetadataSnafu, InitTimezoneSnafu,
Result, ShutdownDatanodeSnafu, ShutdownFrontendSnafu, StartDatanodeSnafu, StartFrontendSnafu,
StartProcedureManagerSnafu, StartWalOptionsAllocatorSnafu, StopProcedureManagerSnafu,
};
use crate::options::{CliOptions, MixOptions, Options};
@@ -98,6 +99,7 @@ impl SubCommand {
pub struct StandaloneOptions {
pub mode: Mode,
pub enable_telemetry: bool,
pub default_timezone: Option<String>,
pub http: HttpOptions,
pub grpc: GrpcOptions,
pub mysql: MysqlOptions,
@@ -121,6 +123,7 @@ impl Default for StandaloneOptions {
Self {
mode: Mode::Standalone,
enable_telemetry: true,
default_timezone: None,
http: HttpOptions::default(),
grpc: GrpcOptions::default(),
mysql: MysqlOptions::default(),
@@ -147,6 +150,7 @@ impl StandaloneOptions {
fn frontend_options(self) -> FrontendOptions {
FrontendOptions {
mode: self.mode,
default_timezone: self.default_timezone,
http: self.http,
grpc: self.grpc,
mysql: self.mysql,
@@ -369,6 +373,9 @@ impl StartCommand {
info!("Building standalone instance with {opts:#?}");
set_default_timezone(opts.frontend.default_timezone.as_deref())
.context(InitTimezoneSnafu)?;
// Ensure the data_home directory exists.
fs::create_dir_all(path::Path::new(&opts.data_home)).context(CreateDirSnafu {
dir: &opts.data_home,

View File

@@ -10,6 +10,7 @@ chrono-tz = "0.8"
chrono.workspace = true
common-error.workspace = true
common-macro.workspace = true
once_cell.workspace = true
serde = { version = "1.0", features = ["derive"] }
serde_json.workspace = true
snafu.workspace = true

View File

@@ -20,7 +20,7 @@ use chrono::{Days, LocalResult, Months, NaiveDateTime, TimeZone as ChronoTimeZon
use serde::{Deserialize, Serialize};
use crate::error::{Error, InvalidDateStrSnafu, Result};
use crate::timezone::TimeZone;
use crate::timezone::Timezone;
use crate::util::{format_utc_datetime, local_datetime_to_utc};
use crate::{Date, Interval};
@@ -110,11 +110,11 @@ impl DateTime {
NaiveDateTime::from_timestamp_millis(self.0)
}
pub fn to_chrono_datetime_with_timezone(&self, tz: Option<TimeZone>) -> Option<NaiveDateTime> {
pub fn to_chrono_datetime_with_timezone(&self, tz: Option<Timezone>) -> Option<NaiveDateTime> {
let datetime = self.to_chrono_datetime();
datetime.map(|v| match tz {
Some(TimeZone::Offset(offset)) => offset.from_utc_datetime(&v).naive_local(),
Some(TimeZone::Named(tz)) => tz.from_utc_datetime(&v).naive_local(),
Some(Timezone::Offset(offset)) => offset.from_utc_datetime(&v).naive_local(),
Some(Timezone::Named(tz)) => tz.from_utc_datetime(&v).naive_local(),
None => Utc.from_utc_datetime(&v).naive_local(),
})
}
@@ -155,10 +155,11 @@ impl DateTime {
#[cfg(test)]
mod tests {
use super::*;
use crate::timezone::set_default_timezone;
#[test]
pub fn test_new_date_time() {
std::env::set_var("TZ", "Asia/Shanghai");
set_default_timezone(Some("Asia/Shanghai")).unwrap();
assert_eq!("1970-01-01 08:00:00+0800", DateTime::new(0).to_string());
assert_eq!("1970-01-01 08:00:01+0800", DateTime::new(1000).to_string());
assert_eq!("1970-01-01 07:59:59+0800", DateTime::new(-1000).to_string());
@@ -166,7 +167,7 @@ mod tests {
#[test]
pub fn test_parse_from_string() {
std::env::set_var("TZ", "Asia/Shanghai");
set_default_timezone(Some("Asia/Shanghai")).unwrap();
let time = "1970-01-01 00:00:00+0800";
let dt = DateTime::from_str(time).unwrap();
assert_eq!(time, &dt.to_string());
@@ -194,7 +195,7 @@ mod tests {
#[test]
fn test_parse_local_date_time() {
std::env::set_var("TZ", "Asia/Shanghai");
set_default_timezone(Some("Asia/Shanghai")).unwrap();
assert_eq!(
-28800000,
DateTime::from_str("1970-01-01 00:00:00").unwrap().val()

View File

@@ -51,8 +51,8 @@ pub enum Error {
#[snafu(display("Timestamp arithmetic overflow, msg: {}", msg))]
ArithmeticOverflow { msg: String, location: Location },
#[snafu(display("Invalid time zone offset: {hours}:{minutes}"))]
InvalidTimeZoneOffset {
#[snafu(display("Invalid timezone offset: {hours}:{minutes}"))]
InvalidTimezoneOffset {
hours: i32,
minutes: u32,
location: Location,
@@ -66,8 +66,8 @@ pub enum Error {
location: Location,
},
#[snafu(display("Invalid time zone string {raw}"))]
ParseTimeZoneName { raw: String, location: Location },
#[snafu(display("Invalid timezone string {raw}"))]
ParseTimezoneName { raw: String, location: Location },
}
impl ErrorExt for Error {
@@ -75,9 +75,9 @@ impl ErrorExt for Error {
match self {
Error::ParseDateStr { .. }
| Error::ParseTimestamp { .. }
| Error::InvalidTimeZoneOffset { .. }
| Error::InvalidTimezoneOffset { .. }
| Error::ParseOffsetStr { .. }
| Error::ParseTimeZoneName { .. } => StatusCode::InvalidArguments,
| Error::ParseTimezoneName { .. } => StatusCode::InvalidArguments,
Error::TimestampOverflow { .. } => StatusCode::Internal,
Error::InvalidDateStr { .. } | Error::ArithmeticOverflow { .. } => {
StatusCode::InvalidArguments
@@ -96,9 +96,9 @@ impl ErrorExt for Error {
| Error::TimestampOverflow { location, .. }
| Error::ArithmeticOverflow { location, .. } => Some(*location),
Error::ParseDateStr { .. }
| Error::InvalidTimeZoneOffset { .. }
| Error::InvalidTimezoneOffset { .. }
| Error::ParseOffsetStr { .. }
| Error::ParseTimeZoneName { .. } => None,
| Error::ParseTimezoneName { .. } => None,
Error::InvalidDateStr { location, .. } => Some(*location),
Error::ParseInterval { location, .. } => Some(*location),
}

View File

@@ -31,4 +31,4 @@ pub use interval::Interval;
pub use range::RangeMillis;
pub use timestamp::Timestamp;
pub use timestamp_millis::TimestampMillis;
pub use timezone::TimeZone;
pub use timezone::Timezone;

View File

@@ -19,8 +19,7 @@ use chrono::{NaiveDateTime, NaiveTime, TimeZone as ChronoTimeZone, Utc};
use serde::{Deserialize, Serialize};
use crate::timestamp::TimeUnit;
use crate::timezone::TimeZone;
use crate::util::format_utc_datetime;
use crate::timezone::{get_timezone, Timezone};
/// Time value, represents the elapsed time since midnight in the unit of `TimeUnit`.
#[derive(Debug, Clone, Default, Copy, Serialize, Deserialize)]
@@ -109,30 +108,28 @@ impl Time {
self.as_formatted_string("%H:%M:%S%.f%z", None)
}
/// Format Time for local timezone.
pub fn to_local_string(&self) -> String {
/// Format Time for system timeszone.
pub fn to_system_tz_string(&self) -> String {
self.as_formatted_string("%H:%M:%S%.f", None)
}
/// Format Time for given timezone.
/// When timezone is None, using local time by default.
pub fn to_timezone_aware_string(&self, tz: Option<TimeZone>) -> String {
/// When timezone is None, using system timezone by default.
pub fn to_timezone_aware_string(&self, tz: Option<Timezone>) -> String {
self.as_formatted_string("%H:%M:%S%.f", tz)
}
fn as_formatted_string(self, pattern: &str, timezone: Option<TimeZone>) -> String {
fn as_formatted_string(self, pattern: &str, timezone: Option<Timezone>) -> String {
if let Some(time) = self.to_chrono_time() {
let date = Utc::now().date_naive();
let datetime = NaiveDateTime::new(date, time);
match timezone {
Some(TimeZone::Offset(offset)) => {
match get_timezone(timezone) {
Timezone::Offset(offset) => {
format!("{}", offset.from_utc_datetime(&datetime).format(pattern))
}
Some(TimeZone::Named(tz)) => {
Timezone::Named(tz) => {
format!("{}", tz.from_utc_datetime(&datetime).format(pattern))
}
None => format_utc_datetime(&datetime, pattern),
}
} else {
format!("[Time{}: {}]", self.unit, self.value)
@@ -223,6 +220,7 @@ mod tests {
use serde_json::Value;
use super::*;
use crate::timezone::set_default_timezone;
#[test]
fn test_time() {
@@ -312,33 +310,33 @@ mod tests {
#[test]
fn test_to_iso8601_string() {
std::env::set_var("TZ", "Asia/Shanghai");
set_default_timezone(Some("+10:00")).unwrap();
let time_millis = 1000001;
let ts = Time::new_millisecond(time_millis);
assert_eq!("08:16:40.001+0800", ts.to_iso8601_string());
assert_eq!("10:16:40.001+1000", ts.to_iso8601_string());
let time_millis = 1000;
let ts = Time::new_millisecond(time_millis);
assert_eq!("08:00:01+0800", ts.to_iso8601_string());
assert_eq!("10:00:01+1000", ts.to_iso8601_string());
let time_millis = 1;
let ts = Time::new_millisecond(time_millis);
assert_eq!("08:00:00.001+0800", ts.to_iso8601_string());
assert_eq!("10:00:00.001+1000", ts.to_iso8601_string());
let time_seconds = 9 * 3600;
let ts = Time::new_second(time_seconds);
assert_eq!("17:00:00+0800", ts.to_iso8601_string());
assert_eq!("19:00:00+1000", ts.to_iso8601_string());
let time_seconds = 23 * 3600;
let ts = Time::new_second(time_seconds);
assert_eq!("07:00:00+0800", ts.to_iso8601_string());
assert_eq!("09:00:00+1000", ts.to_iso8601_string());
}
#[test]
fn test_serialize_to_json_value() {
std::env::set_var("TZ", "Asia/Shanghai");
set_default_timezone(Some("+10:00")).unwrap();
assert_eq!(
"08:00:01+0800",
"10:00:01+1000",
match serde_json::Value::from(Time::new(1, TimeUnit::Second)) {
Value::String(s) => s,
_ => unreachable!(),
@@ -346,7 +344,7 @@ mod tests {
);
assert_eq!(
"08:00:00.001+0800",
"10:00:00.001+1000",
match serde_json::Value::from(Time::new(1, TimeUnit::Millisecond)) {
Value::String(s) => s,
_ => unreachable!(),
@@ -354,7 +352,7 @@ mod tests {
);
assert_eq!(
"08:00:00.000001+0800",
"10:00:00.000001+1000",
match serde_json::Value::from(Time::new(1, TimeUnit::Microsecond)) {
Value::String(s) => s,
_ => unreachable!(),
@@ -362,7 +360,7 @@ mod tests {
);
assert_eq!(
"08:00:00.000000001+0800",
"10:00:00.000000001+1000",
match serde_json::Value::from(Time::new(1, TimeUnit::Nanosecond)) {
Value::String(s) => s,
_ => unreachable!(),
@@ -372,46 +370,47 @@ mod tests {
#[test]
fn test_to_timezone_aware_string() {
std::env::set_var("TZ", "Asia/Shanghai");
set_default_timezone(Some("+10:00")).unwrap();
assert_eq!(
"08:00:00.001",
"10:00:00.001",
Time::new(1, TimeUnit::Millisecond).to_timezone_aware_string(None)
);
std::env::set_var("TZ", "Asia/Shanghai");
assert_eq!(
"08:00:00.001",
Time::new(1, TimeUnit::Millisecond)
.to_timezone_aware_string(TimeZone::from_tz_string("SYSTEM").unwrap())
.to_timezone_aware_string(Some(Timezone::from_tz_string("SYSTEM").unwrap()))
);
assert_eq!(
"08:00:00.001",
Time::new(1, TimeUnit::Millisecond)
.to_timezone_aware_string(TimeZone::from_tz_string("+08:00").unwrap())
.to_timezone_aware_string(Some(Timezone::from_tz_string("+08:00").unwrap()))
);
assert_eq!(
"07:00:00.001",
Time::new(1, TimeUnit::Millisecond)
.to_timezone_aware_string(TimeZone::from_tz_string("+07:00").unwrap())
.to_timezone_aware_string(Some(Timezone::from_tz_string("+07:00").unwrap()))
);
assert_eq!(
"23:00:00.001",
Time::new(1, TimeUnit::Millisecond)
.to_timezone_aware_string(TimeZone::from_tz_string("-01:00").unwrap())
.to_timezone_aware_string(Some(Timezone::from_tz_string("-01:00").unwrap()))
);
assert_eq!(
"08:00:00.001",
Time::new(1, TimeUnit::Millisecond)
.to_timezone_aware_string(TimeZone::from_tz_string("Asia/Shanghai").unwrap())
.to_timezone_aware_string(Some(Timezone::from_tz_string("Asia/Shanghai").unwrap()))
);
assert_eq!(
"00:00:00.001",
Time::new(1, TimeUnit::Millisecond)
.to_timezone_aware_string(TimeZone::from_tz_string("UTC").unwrap())
.to_timezone_aware_string(Some(Timezone::from_tz_string("UTC").unwrap()))
);
assert_eq!(
"03:00:00.001",
Time::new(1, TimeUnit::Millisecond)
.to_timezone_aware_string(TimeZone::from_tz_string("Europe/Moscow").unwrap())
.to_timezone_aware_string(Some(Timezone::from_tz_string("Europe/Moscow").unwrap()))
);
}
}

View File

@@ -27,12 +27,12 @@ use serde::{Deserialize, Serialize};
use snafu::{OptionExt, ResultExt};
use crate::error::{ArithmeticOverflowSnafu, Error, ParseTimestampSnafu, TimestampOverflowSnafu};
use crate::timezone::TimeZone;
use crate::util::{div_ceil, format_utc_datetime};
use crate::timezone::{get_timezone, Timezone};
use crate::util::div_ceil;
use crate::{error, Interval};
/// Timestamp represents the value of units(seconds/milliseconds/microseconds/nanoseconds) elapsed
/// since UNIX epoch. The valid value range of [Timestamp] depends on it's unit (all in UTC time zone):
/// since UNIX epoch. The valid value range of [Timestamp] depends on it's unit (all in UTC timezone):
/// - for [TimeUnit::Second]: [-262144-01-01 00:00:00, +262143-12-31 23:59:59]
/// - for [TimeUnit::Millisecond]: [-262144-01-01 00:00:00.000, +262143-12-31 23:59:59.999]
/// - for [TimeUnit::Microsecond]: [-262144-01-01 00:00:00.000000, +262143-12-31 23:59:59.999999]
@@ -293,26 +293,26 @@ impl Timestamp {
self.as_formatted_string("%Y-%m-%d %H:%M:%S%.f%z", None)
}
/// Format timestamp use **system timezone**.
pub fn to_local_string(&self) -> String {
self.as_formatted_string("%Y-%m-%d %H:%M:%S%.f", None)
}
/// Format timestamp for given timezone.
/// When timezone is None, using local time by default.
pub fn to_timezone_aware_string(&self, tz: Option<TimeZone>) -> String {
/// If `tz==None`, the server default timezone will used.
pub fn to_timezone_aware_string(&self, tz: Option<Timezone>) -> String {
self.as_formatted_string("%Y-%m-%d %H:%M:%S%.f", tz)
}
fn as_formatted_string(self, pattern: &str, timezone: Option<TimeZone>) -> String {
fn as_formatted_string(self, pattern: &str, timezone: Option<Timezone>) -> String {
if let Some(v) = self.to_chrono_datetime() {
match timezone {
Some(TimeZone::Offset(offset)) => {
match get_timezone(timezone) {
Timezone::Offset(offset) => {
format!("{}", offset.from_utc_datetime(&v).format(pattern))
}
Some(TimeZone::Named(tz)) => {
Timezone::Named(tz) => {
format!("{}", tz.from_utc_datetime(&v).format(pattern))
}
None => format_utc_datetime(&v, pattern),
}
} else {
format!("[Timestamp{}: {}]", self.unit, self.value)
@@ -324,11 +324,11 @@ impl Timestamp {
NaiveDateTime::from_timestamp_opt(sec, nsec)
}
pub fn to_chrono_datetime_with_timezone(&self, tz: Option<TimeZone>) -> Option<NaiveDateTime> {
pub fn to_chrono_datetime_with_timezone(&self, tz: Option<Timezone>) -> Option<NaiveDateTime> {
let datetime = self.to_chrono_datetime();
datetime.map(|v| match tz {
Some(TimeZone::Offset(offset)) => offset.from_utc_datetime(&v).naive_local(),
Some(TimeZone::Named(tz)) => tz.from_utc_datetime(&v).naive_local(),
Some(Timezone::Offset(offset)) => offset.from_utc_datetime(&v).naive_local(),
Some(Timezone::Named(tz)) => tz.from_utc_datetime(&v).naive_local(),
None => Utc.from_utc_datetime(&v).naive_local(),
})
}
@@ -560,6 +560,7 @@ mod tests {
use serde_json::Value;
use super::*;
use crate::timezone::set_default_timezone;
#[test]
pub fn test_time_unit() {
@@ -789,7 +790,7 @@ mod tests {
#[test]
fn test_to_iso8601_string() {
std::env::set_var("TZ", "Asia/Shanghai");
set_default_timezone(Some("Asia/Shanghai")).unwrap();
let datetime_str = "2020-09-08 13:42:29.042+0000";
let ts = Timestamp::from_str(datetime_str).unwrap();
assert_eq!("2020-09-08 21:42:29.042+0800", ts.to_iso8601_string());
@@ -813,7 +814,7 @@ mod tests {
#[test]
fn test_serialize_to_json_value() {
std::env::set_var("TZ", "Asia/Shanghai");
set_default_timezone(Some("Asia/Shanghai")).unwrap();
assert_eq!(
"1970-01-01 08:00:01+0800",
match serde_json::Value::from(Timestamp::new(1, TimeUnit::Second)) {
@@ -1054,7 +1055,7 @@ mod tests {
// $TZ doesn't take effort.
#[test]
fn test_parse_in_time_zone() {
fn test_parse_in_timezone() {
std::env::set_var("TZ", "Asia/Shanghai");
assert_eq!(
Timestamp::new(28800, TimeUnit::Second),
@@ -1074,7 +1075,7 @@ mod tests {
#[test]
fn test_to_local_string() {
std::env::set_var("TZ", "Asia/Shanghai");
set_default_timezone(Some("Asia/Shanghai")).unwrap();
assert_eq!(
"1970-01-01 08:00:00.000000001",
@@ -1107,51 +1108,52 @@ mod tests {
#[test]
fn test_to_timezone_aware_string() {
set_default_timezone(Some("Asia/Shanghai")).unwrap();
std::env::set_var("TZ", "Asia/Shanghai");
assert_eq!(
"1970-01-01 08:00:00.001",
Timestamp::new(1, TimeUnit::Millisecond).to_timezone_aware_string(None)
Timestamp::new(1, TimeUnit::Millisecond)
.to_timezone_aware_string(Some(Timezone::from_tz_string("SYSTEM").unwrap()))
);
assert_eq!(
"1970-01-01 08:00:00.001",
Timestamp::new(1, TimeUnit::Millisecond)
.to_timezone_aware_string(TimeZone::from_tz_string("SYSTEM").unwrap())
.to_timezone_aware_string(Some(Timezone::from_tz_string("SYSTEM").unwrap()))
);
assert_eq!(
"1970-01-01 08:00:00.001",
Timestamp::new(1, TimeUnit::Millisecond)
.to_timezone_aware_string(TimeZone::from_tz_string("+08:00").unwrap())
.to_timezone_aware_string(Some(Timezone::from_tz_string("+08:00").unwrap()))
);
assert_eq!(
"1970-01-01 07:00:00.001",
Timestamp::new(1, TimeUnit::Millisecond)
.to_timezone_aware_string(TimeZone::from_tz_string("+07:00").unwrap())
.to_timezone_aware_string(Some(Timezone::from_tz_string("+07:00").unwrap()))
);
assert_eq!(
"1969-12-31 23:00:00.001",
Timestamp::new(1, TimeUnit::Millisecond)
.to_timezone_aware_string(TimeZone::from_tz_string("-01:00").unwrap())
.to_timezone_aware_string(Some(Timezone::from_tz_string("-01:00").unwrap()))
);
assert_eq!(
"1970-01-01 08:00:00.001",
Timestamp::new(1, TimeUnit::Millisecond)
.to_timezone_aware_string(TimeZone::from_tz_string("Asia/Shanghai").unwrap())
.to_timezone_aware_string(Some(Timezone::from_tz_string("Asia/Shanghai").unwrap()))
);
assert_eq!(
"1970-01-01 00:00:00.001",
Timestamp::new(1, TimeUnit::Millisecond)
.to_timezone_aware_string(TimeZone::from_tz_string("UTC").unwrap())
.to_timezone_aware_string(Some(Timezone::from_tz_string("UTC").unwrap()))
);
assert_eq!(
"1970-01-01 01:00:00.001",
Timestamp::new(1, TimeUnit::Millisecond)
.to_timezone_aware_string(TimeZone::from_tz_string("Europe/Berlin").unwrap())
.to_timezone_aware_string(Some(Timezone::from_tz_string("Europe/Berlin").unwrap()))
);
assert_eq!(
"1970-01-01 03:00:00.001",
Timestamp::new(1, TimeUnit::Millisecond)
.to_timezone_aware_string(TimeZone::from_tz_string("Europe/Moscow").unwrap())
.to_timezone_aware_string(Some(Timezone::from_tz_string("Europe/Moscow").unwrap()))
);
}

View File

@@ -15,24 +15,52 @@
use std::fmt::Display;
use std::str::FromStr;
use chrono::{FixedOffset, Local, Offset};
use chrono::FixedOffset;
use chrono_tz::Tz;
use once_cell::sync::OnceCell;
use snafu::{OptionExt, ResultExt};
use crate::error::{
InvalidTimeZoneOffsetSnafu, ParseOffsetStrSnafu, ParseTimeZoneNameSnafu, Result,
InvalidTimezoneOffsetSnafu, ParseOffsetStrSnafu, ParseTimezoneNameSnafu, Result,
};
use crate::util::find_tz_from_env;
/// System timezone in `frontend`/`standalone`,
/// config by option `default_timezone` in toml,
/// default value is `UTC` when `default_timezone` is not set.
static DEFAULT_TIMEZONE: OnceCell<Timezone> = OnceCell::new();
// Set the System timezone by `tz_str`
pub fn set_default_timezone(tz_str: Option<&str>) -> Result<()> {
let tz = match tz_str {
None | Some("") => Timezone::Named(Tz::UTC),
Some(tz) => Timezone::from_tz_string(tz)?,
};
DEFAULT_TIMEZONE.get_or_init(|| tz);
Ok(())
}
#[inline(always)]
/// If the `tz=Some(timezone)`, return `timezone` directly,
/// or return current system timezone.
pub fn get_timezone(tz: Option<Timezone>) -> Timezone {
tz.unwrap_or_else(|| {
DEFAULT_TIMEZONE
.get()
.cloned()
.unwrap_or(Timezone::Named(Tz::UTC))
})
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum TimeZone {
pub enum Timezone {
Offset(FixedOffset),
Named(Tz),
}
impl TimeZone {
impl Timezone {
/// Compute timezone from given offset hours and minutes
/// Return `None` if given offset exceeds scope
/// Return `Err` if given offset exceeds scope
pub fn hours_mins_opt(offset_hours: i32, offset_mins: u32) -> Result<Self> {
let offset_secs = if offset_hours > 0 {
offset_hours * 3600 + offset_mins as i32 * 60
@@ -42,7 +70,7 @@ impl TimeZone {
FixedOffset::east_opt(offset_secs)
.map(Self::Offset)
.context(InvalidTimeZoneOffsetSnafu {
.context(InvalidTimezoneOffsetSnafu {
hours: offset_hours,
minutes: offset_mins,
})
@@ -57,10 +85,10 @@ impl TimeZone {
/// - `SYSTEM`
/// - Offset to UTC: `+08:00` , `-11:30`
/// - Named zones: `Asia/Shanghai`, `Europe/Berlin`
pub fn from_tz_string(tz_string: &str) -> Result<Option<Self>> {
pub fn from_tz_string(tz_string: &str) -> Result<Self> {
// Use system timezone
if tz_string.eq_ignore_ascii_case("SYSTEM") {
Ok(None)
Ok(Timezone::Named(find_tz_from_env().unwrap_or(Tz::UTC)))
} else if let Some((hrs, mins)) = tz_string.split_once(':') {
let hrs = hrs
.parse::<i32>()
@@ -68,16 +96,16 @@ impl TimeZone {
let mins = mins
.parse::<u32>()
.context(ParseOffsetStrSnafu { raw: tz_string })?;
Self::hours_mins_opt(hrs, mins).map(Some)
Self::hours_mins_opt(hrs, mins)
} else if let Ok(tz) = Tz::from_str(tz_string) {
Ok(Some(Self::Named(tz)))
Ok(Self::Named(tz))
} else {
ParseTimeZoneNameSnafu { raw: tz_string }.fail()
ParseTimezoneNameSnafu { raw: tz_string }.fail()
}
}
}
impl Display for TimeZone {
impl Display for Timezone {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Self::Named(tz) => write!(f, "{}", tz.name()),
@@ -87,12 +115,9 @@ impl Display for TimeZone {
}
#[inline]
pub fn system_time_zone_name() -> String {
if let Some(tz) = find_tz_from_env() {
Local::now().with_timezone(&tz).offset().fix().to_string()
} else {
Local::now().offset().to_string()
}
/// Return current system config timezone, default config is UTC
pub fn system_timezone_name() -> String {
format!("{}", get_timezone(None))
}
#[cfg(test)]
@@ -101,61 +126,56 @@ mod tests {
#[test]
fn test_from_tz_string() {
assert_eq!(None, TimeZone::from_tz_string("SYSTEM").unwrap());
let utc_plus_8 = Some(TimeZone::Offset(FixedOffset::east_opt(3600 * 8).unwrap()));
assert_eq!(utc_plus_8, TimeZone::from_tz_string("+8:00").unwrap());
assert_eq!(utc_plus_8, TimeZone::from_tz_string("+08:00").unwrap());
assert_eq!(utc_plus_8, TimeZone::from_tz_string("08:00").unwrap());
let utc_minus_8 = Some(TimeZone::Offset(FixedOffset::west_opt(3600 * 8).unwrap()));
assert_eq!(utc_minus_8, TimeZone::from_tz_string("-08:00").unwrap());
assert_eq!(utc_minus_8, TimeZone::from_tz_string("-8:00").unwrap());
let utc_minus_8_5 = Some(TimeZone::Offset(
FixedOffset::west_opt(3600 * 8 + 60 * 30).unwrap(),
));
assert_eq!(utc_minus_8_5, TimeZone::from_tz_string("-8:30").unwrap());
let utc_plus_max = Some(TimeZone::Offset(FixedOffset::east_opt(3600 * 14).unwrap()));
assert_eq!(utc_plus_max, TimeZone::from_tz_string("14:00").unwrap());
let utc_minus_max = Some(TimeZone::Offset(
FixedOffset::west_opt(3600 * 13 + 60 * 59).unwrap(),
));
assert_eq!(utc_minus_max, TimeZone::from_tz_string("-13:59").unwrap());
assert_eq!(
Some(TimeZone::Named(Tz::Asia__Shanghai)),
TimeZone::from_tz_string("Asia/Shanghai").unwrap()
);
assert_eq!(
Some(TimeZone::Named(Tz::UTC)),
TimeZone::from_tz_string("UTC").unwrap()
Timezone::Named(Tz::UTC),
Timezone::from_tz_string("SYSTEM").unwrap()
);
assert!(TimeZone::from_tz_string("WORLD_PEACE").is_err());
assert!(TimeZone::from_tz_string("A0:01").is_err());
assert!(TimeZone::from_tz_string("20:0A").is_err());
assert!(TimeZone::from_tz_string(":::::").is_err());
assert!(TimeZone::from_tz_string("Asia/London").is_err());
assert!(TimeZone::from_tz_string("Unknown").is_err());
let utc_plus_8 = Timezone::Offset(FixedOffset::east_opt(3600 * 8).unwrap());
assert_eq!(utc_plus_8, Timezone::from_tz_string("+8:00").unwrap());
assert_eq!(utc_plus_8, Timezone::from_tz_string("+08:00").unwrap());
assert_eq!(utc_plus_8, Timezone::from_tz_string("08:00").unwrap());
let utc_minus_8 = Timezone::Offset(FixedOffset::west_opt(3600 * 8).unwrap());
assert_eq!(utc_minus_8, Timezone::from_tz_string("-08:00").unwrap());
assert_eq!(utc_minus_8, Timezone::from_tz_string("-8:00").unwrap());
let utc_minus_8_5 = Timezone::Offset(FixedOffset::west_opt(3600 * 8 + 60 * 30).unwrap());
assert_eq!(utc_minus_8_5, Timezone::from_tz_string("-8:30").unwrap());
let utc_plus_max = Timezone::Offset(FixedOffset::east_opt(3600 * 14).unwrap());
assert_eq!(utc_plus_max, Timezone::from_tz_string("14:00").unwrap());
let utc_minus_max = Timezone::Offset(FixedOffset::west_opt(3600 * 13 + 60 * 59).unwrap());
assert_eq!(utc_minus_max, Timezone::from_tz_string("-13:59").unwrap());
assert_eq!(
Timezone::Named(Tz::Asia__Shanghai),
Timezone::from_tz_string("Asia/Shanghai").unwrap()
);
assert_eq!(
Timezone::Named(Tz::UTC),
Timezone::from_tz_string("UTC").unwrap()
);
assert!(Timezone::from_tz_string("WORLD_PEACE").is_err());
assert!(Timezone::from_tz_string("A0:01").is_err());
assert!(Timezone::from_tz_string("20:0A").is_err());
assert!(Timezone::from_tz_string(":::::").is_err());
assert!(Timezone::from_tz_string("Asia/London").is_err());
assert!(Timezone::from_tz_string("Unknown").is_err());
}
#[test]
fn test_timezone_to_string() {
assert_eq!("UTC", TimeZone::Named(Tz::UTC).to_string());
assert_eq!("UTC", Timezone::Named(Tz::UTC).to_string());
assert_eq!(
"+01:00",
TimeZone::from_tz_string("01:00")
.unwrap()
.unwrap()
.to_string()
Timezone::from_tz_string("01:00").unwrap().to_string()
);
assert_eq!(
"Asia/Shanghai",
TimeZone::from_tz_string("Asia/Shanghai")
.unwrap()
Timezone::from_tz_string("Asia/Shanghai")
.unwrap()
.to_string()
);

View File

@@ -14,23 +14,24 @@
use std::str::FromStr;
use chrono::offset::Local;
use chrono::{LocalResult, NaiveDateTime, TimeZone};
use chrono_tz::Tz;
use crate::timezone::get_timezone;
pub fn format_utc_datetime(utc: &NaiveDateTime, pattern: &str) -> String {
if let Some(tz) = find_tz_from_env() {
format!("{}", tz.from_utc_datetime(utc).format(pattern))
} else {
format!("{}", Local.from_utc_datetime(utc).format(pattern))
match get_timezone(None) {
crate::Timezone::Offset(offset) => {
offset.from_utc_datetime(utc).format(pattern).to_string()
}
crate::Timezone::Named(tz) => tz.from_utc_datetime(utc).format(pattern).to_string(),
}
}
pub fn local_datetime_to_utc(local: &NaiveDateTime) -> LocalResult<NaiveDateTime> {
if let Some(tz) = find_tz_from_env() {
tz.from_local_datetime(local).map(|x| x.naive_utc())
} else {
Local.from_local_datetime(local).map(|x| x.naive_utc())
match get_timezone(None) {
crate::Timezone::Offset(offset) => offset.from_local_datetime(local).map(|x| x.naive_utc()),
crate::Timezone::Named(tz) => tz.from_local_datetime(local).map(|x| x.naive_utc()),
}
}

View File

@@ -120,11 +120,13 @@ define_time_with_unit!(Nanosecond, i64);
#[cfg(test)]
mod tests {
use common_time::timezone::set_default_timezone;
use super::*;
#[test]
fn test_to_serde_json_value() {
std::env::set_var("TZ", "Asia/Shanghai");
set_default_timezone(Some("Asia/Shanghai")).unwrap();
let time = TimeSecond::new(123);
let val = serde_json::Value::from(time);
match val {

View File

@@ -122,11 +122,13 @@ define_timestamp_with_unit!(Nanosecond);
#[cfg(test)]
mod tests {
use common_time::timezone::set_default_timezone;
use super::*;
#[test]
fn test_to_serde_json_value() {
std::env::set_var("TZ", "Asia/Shanghai");
set_default_timezone(Some("Asia/Shanghai")).unwrap();
let ts = TimestampSecond::new(123);
let val = serde_json::Value::from(ts);
match val {

View File

@@ -176,6 +176,7 @@ mod tests {
use common_base::bytes::StringBytes;
use common_time::time::Time;
use common_time::timezone::set_default_timezone;
use common_time::{Date, DateTime, Timestamp};
use ordered_float::OrderedFloat;
@@ -213,7 +214,7 @@ mod tests {
#[test]
fn test_cast_with_opt() {
std::env::set_var("TZ", "Asia/Shanghai");
set_default_timezone(Some("Asia/Shanghai")).unwrap();
// non-strict mode
let cast_option = CastOption { strict: false };
let src_value = Value::Int8(-1);

View File

@@ -101,6 +101,7 @@ impl LogicalPrimitiveType for DateType {
#[cfg(test)]
mod tests {
use common_base::bytes::StringBytes;
use common_time::timezone::set_default_timezone;
use common_time::Timestamp;
use super::*;
@@ -108,7 +109,7 @@ mod tests {
// $TZ doesn't take effort
#[test]
fn test_date_cast() {
std::env::set_var("TZ", "Asia/Shanghai");
set_default_timezone(Some("Asia/Shanghai")).unwrap();
// timestamp -> date
let ts = Value::Timestamp(Timestamp::from_str("2000-01-01 08:00:01").unwrap());
let date = ConcreteDataType::date_datatype().try_cast(ts).unwrap();

View File

@@ -101,6 +101,7 @@ impl LogicalPrimitiveType for DateTimeType {
#[cfg(test)]
mod tests {
use common_time::timezone::set_default_timezone;
use common_time::Timestamp;
use super::*;
@@ -113,7 +114,7 @@ mod tests {
assert_eq!(dt, Value::DateTime(DateTime::from(1000)));
// cast from String
std::env::set_var("TZ", "Asia/Shanghai");
set_default_timezone(Some("Asia/Shanghai")).unwrap();
let val = Value::String("1970-01-01 00:00:00+0800".into());
let dt = ConcreteDataType::datetime_datatype().try_cast(val).unwrap();
assert_eq!(

View File

@@ -203,6 +203,7 @@ impl_data_type_for_timestamp!(Microsecond);
#[cfg(test)]
mod tests {
use common_time::timezone::set_default_timezone;
use common_time::{Date, DateTime};
use super::*;
@@ -230,7 +231,7 @@ mod tests {
// $TZ doesn't take effort
#[test]
fn test_timestamp_cast() {
std::env::set_var("TZ", "Asia/Shanghai");
set_default_timezone(Some("Asia/Shanghai")).unwrap();
// String -> TimestampSecond
let s = Value::String("2021-01-01 01:02:03".to_string().into());
let ts = ConcreteDataType::timestamp_second_datatype()

View File

@@ -1190,6 +1190,7 @@ impl<'a> ValueRef<'a> {
#[cfg(test)]
mod tests {
use arrow::datatypes::DataType as ArrowDataType;
use common_time::timezone::set_default_timezone;
use num_traits::Float;
use super::*;
@@ -1875,7 +1876,7 @@ mod tests {
#[test]
fn test_display() {
std::env::set_var("TZ", "Asia/Shanghai");
set_default_timezone(Some("Asia/Shanghai")).unwrap();
assert_eq!(Value::Null.to_string(), "Null");
assert_eq!(Value::UInt8(8).to_string(), "8");
assert_eq!(Value::UInt16(16).to_string(), "16");

View File

@@ -26,6 +26,7 @@ mod tests {
use arrow::array::{Array, PrimitiveArray};
use arrow_array::ArrayRef;
use common_time::timezone::set_default_timezone;
use common_time::DateTime;
use super::*;
@@ -37,7 +38,7 @@ mod tests {
#[test]
fn test_datetime_vector() {
std::env::set_var("TZ", "Asia/Shanghai");
set_default_timezone(Some("Asia/Shanghai")).unwrap();
let v = DateTimeVector::new(PrimitiveArray::from(vec![1000, 2000, 3000]));
assert_eq!(ConcreteDataType::datetime_datatype(), v.data_type());
assert_eq!(3, v.len());

View File

@@ -32,6 +32,7 @@ use crate::service_config::{
pub struct FrontendOptions {
pub mode: Mode,
pub node_id: Option<String>,
pub default_timezone: Option<String>,
pub heartbeat: HeartbeatOptions,
pub http: HttpOptions,
pub grpc: GrpcOptions,
@@ -53,6 +54,7 @@ impl Default for FrontendOptions {
Self {
mode: Mode::Standalone,
node_id: None,
default_timezone: None,
heartbeat: HeartbeatOptions::frontend_default(),
http: HttpOptions::default(),
grpc: GrpcOptions::default(),

View File

@@ -21,8 +21,8 @@ use std::sync::Arc;
use common_query::Output;
use common_recordbatch::RecordBatches;
use common_time::timezone::system_time_zone_name;
use common_time::TimeZone;
use common_time::timezone::system_timezone_name;
use common_time::Timezone;
use datatypes::prelude::ConcreteDataType;
use datatypes::schema::{ColumnSchema, Schema};
use datatypes::vectors::StringVector;
@@ -55,7 +55,7 @@ static SELECT_TIME_DIFF_FUNC_PATTERN: Lazy<Regex> =
static SHOW_SQL_MODE_PATTERN: Lazy<Regex> =
Lazy::new(|| Regex::new("(?i)^(SHOW VARIABLES LIKE 'sql_mode'(.*))").unwrap());
// Time zone settings
// Timezone settings
static SET_TIME_ZONE_PATTERN: Lazy<Regex> =
Lazy::new(|| Regex::new(r"(?i)^SET TIME_ZONE\s*=\s*'(\S+)'").unwrap());
@@ -200,11 +200,8 @@ fn select_variable(query: &str, query_context: QueryContextRef) -> Option<Output
// get value of variables from known sources or fallback to defaults
let value = match var_as[0] {
"time_zone" => query_context
.time_zone()
.map(|tz| tz.to_string())
.unwrap_or_else(|| "".to_owned()),
"system_time_zone" => system_time_zone_name(),
"time_zone" => query_context.timezone().to_string(),
"system_time_zone" => system_timezone_name(),
_ => VAR_VALUES
.get(var_as[0])
.map(|v| v.to_string())
@@ -271,8 +268,8 @@ fn check_set_variables(query: &str, session: SessionRef) -> Option<Output> {
if let Some(captures) = SET_TIME_ZONE_PATTERN.captures(query) {
// get the capture
let tz = captures.get(1).unwrap();
if let Ok(timezone) = TimeZone::from_tz_string(tz.as_str()) {
session.set_time_zone(timezone);
if let Ok(timezone) = Timezone::from_tz_string(tz.as_str()) {
session.set_timezone(timezone);
return Some(Output::AffectedRows(0));
}
}
@@ -331,6 +328,7 @@ fn get_version() -> String {
#[cfg(test)]
mod test {
use common_time::timezone::set_default_timezone;
use session::context::{Channel, QueryContext};
use session::Session;
@@ -390,16 +388,16 @@ mod test {
+-----------------+------------------------+";
test(query, expected);
// set sysstem timezone
std::env::set_var("TZ", "Asia/Shanghai");
// set system timezone
set_default_timezone(Some("Asia/Shanghai")).unwrap();
// complex variables
let query = "/* mysql-connector-java-8.0.17 (Revision: 16a712ddb3f826a1933ab42b0039f7fb9eebc6ec) */SELECT @@session.auto_increment_increment AS auto_increment_increment, @@character_set_client AS character_set_client, @@character_set_connection AS character_set_connection, @@character_set_results AS character_set_results, @@character_set_server AS character_set_server, @@collation_server AS collation_server, @@collation_connection AS collation_connection, @@init_connect AS init_connect, @@interactive_timeout AS interactive_timeout, @@license AS license, @@lower_case_table_names AS lower_case_table_names, @@max_allowed_packet AS max_allowed_packet, @@net_write_timeout AS net_write_timeout, @@performance_schema AS performance_schema, @@sql_mode AS sql_mode, @@system_time_zone AS system_time_zone, @@time_zone AS time_zone, @@transaction_isolation AS transaction_isolation, @@wait_timeout AS wait_timeout;";
let expected = "\
+--------------------------+----------------------+--------------------------+-----------------------+----------------------+------------------+----------------------+--------------+---------------------+---------+------------------------+--------------------+-------------------+--------------------+----------+------------------+-----------+-----------------------+---------------+
| auto_increment_increment | character_set_client | character_set_connection | character_set_results | character_set_server | collation_server | collation_connection | init_connect | interactive_timeout | license | lower_case_table_names | max_allowed_packet | net_write_timeout | performance_schema | sql_mode | system_time_zone | time_zone | transaction_isolation | wait_timeout; |
+--------------------------+----------------------+--------------------------+-----------------------+----------------------+------------------+----------------------+--------------+---------------------+---------+------------------------+--------------------+-------------------+--------------------+----------+------------------+-----------+-----------------------+---------------+
| 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 31536000 | 0 | 0 | 134217728 | 31536000 | 0 | 0 | +08:00 | | REPEATABLE-READ | 31536000 |
+--------------------------+----------------------+--------------------------+-----------------------+----------------------+------------------+----------------------+--------------+---------------------+---------+------------------------+--------------------+-------------------+--------------------+----------+------------------+-----------+-----------------------+---------------+";
+--------------------------+----------------------+--------------------------+-----------------------+----------------------+------------------+----------------------+--------------+---------------------+---------+------------------------+--------------------+-------------------+--------------------+----------+------------------+---------------+-----------------------+---------------+
| auto_increment_increment | character_set_client | character_set_connection | character_set_results | character_set_server | collation_server | collation_connection | init_connect | interactive_timeout | license | lower_case_table_names | max_allowed_packet | net_write_timeout | performance_schema | sql_mode | system_time_zone | time_zone | transaction_isolation | wait_timeout; |
+--------------------------+----------------------+--------------------------+-----------------------+----------------------+------------------+----------------------+--------------+---------------------+---------+------------------------+--------------------+-------------------+--------------------+----------+------------------+---------------+-----------------------+---------------+
| 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 31536000 | 0 | 0 | 134217728 | 31536000 | 0 | 0 | Asia/Shanghai | Asia/Shanghai | REPEATABLE-READ | 31536000 |
+--------------------------+----------------------+--------------------------+-----------------------+----------------------+------------------+----------------------+--------------+---------------------+---------+------------------------+--------------------+-------------------+--------------------+----------+------------------+---------------+-----------------------+---------------+";
test(query, expected);
let query = "show variables";
@@ -437,8 +435,17 @@ mod test {
}
#[test]
fn test_set_time_zone() {
fn test_set_timezone() {
// test default is UTC when no config in greptimedb
{
let session = Arc::new(Session::new(None, Channel::Mysql));
let query_context = session.new_query_context();
assert_eq!("UTC", query_context.timezone().to_string());
}
set_default_timezone(Some("Asia/Shanghai")).unwrap();
let session = Arc::new(Session::new(None, Channel::Mysql));
let query_context = session.new_query_context();
assert_eq!("Asia/Shanghai", query_context.timezone().to_string());
let output = check(
"set time_zone = 'UTC'",
QueryContext::arc(),
@@ -451,7 +458,7 @@ mod test {
_ => unreachable!(),
}
let query_context = session.new_query_context();
assert_eq!("UTC", query_context.time_zone().unwrap().to_string());
assert_eq!("UTC", query_context.timezone().to_string());
let output = check("select @@time_zone", query_context.clone(), session.clone());
match output.unwrap() {

View File

@@ -193,10 +193,12 @@ impl<'a, W: AsyncWrite + Unpin> MysqlResultWriter<'a, W> {
Value::Binary(v) => row_writer.write_col(v.deref())?,
Value::Date(v) => row_writer.write_col(v.to_chrono_date())?,
// convert datetime and timestamp to timezone of current connection
Value::DateTime(v) => row_writer
.write_col(v.to_chrono_datetime_with_timezone(query_context.time_zone()))?,
Value::Timestamp(v) => row_writer
.write_col(v.to_chrono_datetime_with_timezone(query_context.time_zone()))?,
Value::DateTime(v) => row_writer.write_col(
v.to_chrono_datetime_with_timezone(Some(query_context.timezone())),
)?,
Value::Timestamp(v) => row_writer.write_col(
v.to_chrono_datetime_with_timezone(Some(query_context.timezone())),
)?,
Value::Interval(v) => row_writer.write_col(v.to_iso8601_string())?,
Value::Duration(v) => row_writer.write_col(v.to_std_duration())?,
Value::List(_) => {
@@ -208,7 +210,7 @@ impl<'a, W: AsyncWrite + Unpin> MysqlResultWriter<'a, W> {
})
}
Value::Time(v) => row_writer
.write_col(v.to_timezone_aware_string(query_context.time_zone()))?,
.write_col(v.to_timezone_aware_string(Some(query_context.timezone())))?,
Value::Decimal128(v) => row_writer.write_col(v.to_string())?,
}
}

View File

@@ -21,7 +21,8 @@ use arc_swap::ArcSwap;
use auth::UserInfoRef;
use common_catalog::consts::{DEFAULT_CATALOG_NAME, DEFAULT_SCHEMA_NAME};
use common_catalog::{build_db_string, parse_catalog_and_schema_from_db_string};
use common_time::TimeZone;
use common_time::timezone::get_timezone;
use common_time::Timezone;
use derive_builder::Builder;
use sql::dialect::{Dialect, GreptimeDbDialect, MySqlDialect, PostgreSqlDialect};
@@ -35,7 +36,7 @@ pub struct QueryContext {
current_catalog: String,
current_schema: String,
current_user: ArcSwap<Option<UserInfoRef>>,
time_zone: Option<TimeZone>,
timezone: Timezone,
sql_dialect: Box<dyn Dialect + Send + Sync>,
}
@@ -57,7 +58,7 @@ impl From<&RegionRequestHeader> for QueryContext {
current_catalog: catalog.to_string(),
current_schema: schema.to_string(),
current_user: Default::default(),
time_zone: Default::default(),
timezone: get_timezone(None),
sql_dialect: Box::new(GreptimeDbDialect {}),
}
}
@@ -115,8 +116,8 @@ impl QueryContext {
}
#[inline]
pub fn time_zone(&self) -> Option<TimeZone> {
self.time_zone.clone()
pub fn timezone(&self) -> Timezone {
self.timezone.clone()
}
#[inline]
@@ -142,7 +143,7 @@ impl QueryContextBuilder {
current_user: self
.current_user
.unwrap_or_else(|| ArcSwap::new(Arc::new(None))),
time_zone: self.time_zone.unwrap_or(None),
timezone: self.timezone.unwrap_or(get_timezone(None)),
sql_dialect: self
.sql_dialect
.unwrap_or_else(|| Box::new(GreptimeDbDialect {})),

View File

@@ -21,7 +21,8 @@ use arc_swap::ArcSwap;
use auth::UserInfoRef;
use common_catalog::build_db_string;
use common_catalog::consts::{DEFAULT_CATALOG_NAME, DEFAULT_SCHEMA_NAME};
use common_time::TimeZone;
use common_time::timezone::get_timezone;
use common_time::Timezone;
use context::QueryContextBuilder;
use crate::context::{Channel, ConnInfo, QueryContextRef};
@@ -33,7 +34,7 @@ pub struct Session {
schema: ArcSwap<String>,
user_info: ArcSwap<UserInfoRef>,
conn_info: ConnInfo,
time_zone: ArcSwap<Option<TimeZone>>,
timezone: ArcSwap<Timezone>,
}
pub type SessionRef = Arc<Session>;
@@ -45,7 +46,7 @@ impl Session {
schema: ArcSwap::new(Arc::new(DEFAULT_SCHEMA_NAME.into())),
user_info: ArcSwap::new(Arc::new(auth::userinfo_by_name(None))),
conn_info: ConnInfo::new(addr, channel),
time_zone: ArcSwap::new(Arc::new(None)),
timezone: ArcSwap::new(Arc::new(get_timezone(None))),
}
}
@@ -58,7 +59,7 @@ impl Session {
.current_catalog(self.catalog.load().to_string())
.current_schema(self.schema.load().to_string())
.sql_dialect(self.conn_info.channel.dialect())
.time_zone((**self.time_zone.load()).clone())
.timezone((**self.timezone.load()).clone())
.build()
}
@@ -73,13 +74,13 @@ impl Session {
}
#[inline]
pub fn time_zone(&self) -> Option<TimeZone> {
self.time_zone.load().as_ref().clone()
pub fn timezone(&self) -> Timezone {
self.timezone.load().as_ref().clone()
}
#[inline]
pub fn set_time_zone(&self, tz: Option<TimeZone>) {
let _ = self.time_zone.swap(Arc::new(tz));
pub fn set_timezone(&self, tz: Timezone) {
let _ = self.timezone.swap(Arc::new(tz));
}
#[inline]

View File

@@ -521,6 +521,7 @@ mod tests {
use api::v1::ColumnDataType;
use common_time::timestamp::TimeUnit;
use common_time::timezone::set_default_timezone;
use datatypes::types::BooleanType;
use datatypes::value::OrderedFloat;
@@ -696,7 +697,7 @@ mod tests {
#[test]
pub fn test_parse_datetime_literal() {
std::env::set_var("TZ", "Asia/Shanghai");
set_default_timezone(Some("Asia/Shanghai")).unwrap();
let value = sql_value_to_value(
"datetime_col",
&ConcreteDataType::datetime_datatype(),

View File

@@ -219,8 +219,8 @@ pub async fn test_mysql_timezone(store_type: StorageType) {
.unwrap();
let _ = conn.execute("SET time_zone = 'UTC'").await.unwrap();
let time_zone = conn.fetch_all("SELECT @@time_zone").await.unwrap();
assert_eq!(time_zone[0].get::<String, usize>(0), "UTC");
let timezone = conn.fetch_all("SELECT @@time_zone").await.unwrap();
assert_eq!(timezone[0].get::<String, usize>(0), "UTC");
// test data
let _ = conn