fix(data-grid): format Oracle timestamp literals
This commit is contained in:
1 parent
8a5e45150a
commit
754b988ca5
1 file changed
+132
-1
@@ -858,6 +858,13 @@ pub fn format_grid_sql_literal(
|
||||
let escaped = text.replace('\\', "\\\\").replace('\'', "''");
|
||||
return format!("ST_GeomFromText('{}')", escaped);
|
||||
}
|
||||
if is_oracle_temporal_literal_database(database_type) {
|
||||
if let Some(literal) =
|
||||
format_oracle_temporal_literal(&text, column_info.map(|column| column.data_type.as_str()))
|
||||
{
|
||||
return literal;
|
||||
}
|
||||
}
|
||||
let literal_text = if database_type == Some(DatabaseType::Tdengine) {
|
||||
format_tdengine_timestamp_literal_text(&text)
|
||||
} else if is_mysql_datetime_literal_database(database_type)
|
||||
@@ -875,6 +882,65 @@ pub fn format_grid_sql_literal(
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
enum OracleTemporalKind {
|
||||
Date,
|
||||
Timestamp,
|
||||
TimestampWithTimeZone,
|
||||
}
|
||||
|
||||
fn is_oracle_temporal_literal_database(database_type: Option<DatabaseType>) -> bool {
|
||||
matches!(database_type, Some(DatabaseType::Oracle | DatabaseType::OceanbaseOracle))
|
||||
}
|
||||
|
||||
fn format_oracle_temporal_literal(text: &str, data_type: Option<&str>) -> Option<String> {
|
||||
let kind = oracle_temporal_column_kind(data_type?)?;
|
||||
let parts = regex_like_rfc3339(text)?;
|
||||
let fraction = parts.fraction.unwrap_or_default();
|
||||
let datetime = format!("{} {}{}", parts.date, parts.time, fraction);
|
||||
match kind {
|
||||
OracleTemporalKind::Date => Some(format!("TO_DATE('{} {}', 'YYYY-MM-DD HH24:MI:SS')", parts.date, parts.time)),
|
||||
OracleTemporalKind::Timestamp => {
|
||||
let mask = oracle_timestamp_format_mask(datetime.contains('.'));
|
||||
Some(format!("TO_TIMESTAMP('{datetime}', '{mask}')"))
|
||||
}
|
||||
OracleTemporalKind::TimestampWithTimeZone => {
|
||||
let zone = oracle_timezone_suffix(&parts.zone);
|
||||
let mask = oracle_timestamp_format_mask(datetime.contains('.'));
|
||||
Some(format!("TO_TIMESTAMP_TZ('{datetime} {zone}', '{mask} TZH:TZM')"))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn oracle_temporal_column_kind(data_type: &str) -> Option<OracleTemporalKind> {
|
||||
let lower = data_type.trim().to_ascii_lowercase();
|
||||
let base = lower.split(['(', ' ']).next().unwrap_or("");
|
||||
match base {
|
||||
"date" => Some(OracleTemporalKind::Date),
|
||||
"timestamp" if lower.contains("with time zone") || lower.contains("with local time zone") => {
|
||||
Some(OracleTemporalKind::TimestampWithTimeZone)
|
||||
}
|
||||
"timestamp" => Some(OracleTemporalKind::Timestamp),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
fn oracle_timestamp_format_mask(has_fraction: bool) -> &'static str {
|
||||
if has_fraction {
|
||||
"YYYY-MM-DD HH24:MI:SS.FF"
|
||||
} else {
|
||||
"YYYY-MM-DD HH24:MI:SS"
|
||||
}
|
||||
}
|
||||
|
||||
fn oracle_timezone_suffix(zone: &str) -> String {
|
||||
if zone.eq_ignore_ascii_case("z") {
|
||||
"+00:00".to_string()
|
||||
} else {
|
||||
zone.to_string()
|
||||
}
|
||||
}
|
||||
|
||||
fn is_mysql_bit_literal_column(database_type: Option<DatabaseType>, column_info: Option<&DataGridColumnInfo>) -> bool {
|
||||
is_mysql_datetime_literal_database(database_type)
|
||||
&& column_info.map(|column| is_bit_column_type(&column.data_type)).unwrap_or(false)
|
||||
@@ -994,6 +1060,7 @@ struct Rfc3339Parts {
|
||||
date: String,
|
||||
time: String,
|
||||
fraction: Option<String>,
|
||||
zone: String,
|
||||
}
|
||||
|
||||
fn regex_like_rfc3339(text: &str) -> Option<Rfc3339Parts> {
|
||||
@@ -1021,7 +1088,7 @@ fn regex_like_rfc3339(text: &str) -> Option<Rfc3339Parts> {
|
||||
(None, rest)
|
||||
};
|
||||
if zone == "Z" || zone == "z" || is_timezone_offset(zone) {
|
||||
Some(Rfc3339Parts { date: date.to_string(), time: time.to_string(), fraction })
|
||||
Some(Rfc3339Parts { date: date.to_string(), time: time.to_string(), fraction, zone: zone.to_string() })
|
||||
} else {
|
||||
None
|
||||
}
|
||||
@@ -1738,6 +1805,70 @@ mod tests {
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn formats_oracle_temporal_literals_without_nls_parsing() {
|
||||
let timestamp = column("created_at", "TIMESTAMP(6)", true, None);
|
||||
let timestamp_tz = column("recorded_at", "TIMESTAMP(6) WITH TIME ZONE", true, None);
|
||||
let timestamp_ltz = column("local_recorded_at", "TIMESTAMP(6) WITH LOCAL TIME ZONE", true, None);
|
||||
let date = column("event_day", "DATE", true, None);
|
||||
let text = column("raw_text", "VARCHAR2(64)", true, None);
|
||||
|
||||
assert_eq!(
|
||||
format_grid_sql_literal(&json!("2022-08-25T09:58:43Z"), Some(DatabaseType::Oracle), Some(×tamp)),
|
||||
"TO_TIMESTAMP('2022-08-25 09:58:43', 'YYYY-MM-DD HH24:MI:SS')"
|
||||
);
|
||||
assert_eq!(
|
||||
format_grid_sql_literal(
|
||||
&json!("2022-08-25T09:58:43.123456+08:00"),
|
||||
Some(DatabaseType::Oracle),
|
||||
Some(×tamp_tz)
|
||||
),
|
||||
"TO_TIMESTAMP_TZ('2022-08-25 09:58:43.123456 +08:00', 'YYYY-MM-DD HH24:MI:SS.FF TZH:TZM')"
|
||||
);
|
||||
assert_eq!(
|
||||
format_grid_sql_literal(&json!("2022-08-25T09:58:43Z"), Some(DatabaseType::Oracle), Some(×tamp_ltz)),
|
||||
"TO_TIMESTAMP_TZ('2022-08-25 09:58:43 +00:00', 'YYYY-MM-DD HH24:MI:SS TZH:TZM')"
|
||||
);
|
||||
assert_eq!(
|
||||
format_grid_sql_literal(&json!("2022-08-25T09:58:43Z"), Some(DatabaseType::Oracle), Some(&date)),
|
||||
"TO_DATE('2022-08-25 09:58:43', 'YYYY-MM-DD HH24:MI:SS')"
|
||||
);
|
||||
assert_eq!(
|
||||
format_grid_sql_literal(&json!("2022-08-25T09:58:43Z"), Some(DatabaseType::Oracle), Some(&text)),
|
||||
"'2022-08-25T09:58:43Z'"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn prepares_oracle_timestamp_insert_from_iso_grid_value() {
|
||||
let result = prepare_data_grid_save(DataGridSaveStatementOptions {
|
||||
database_type: Some(DatabaseType::Oracle),
|
||||
table_meta: DataGridTableMeta {
|
||||
schema: Some("APP".to_string()),
|
||||
table_name: "EVENTS".to_string(),
|
||||
primary_keys: vec!["ID".to_string()],
|
||||
columns: Some(vec![
|
||||
column("ID", "NUMBER", false, None),
|
||||
column("CREATED_AT", "TIMESTAMP(6)", true, None),
|
||||
]),
|
||||
},
|
||||
columns: vec!["ID".to_string(), "CREATED_AT".to_string()],
|
||||
source_columns: None,
|
||||
rows: vec![],
|
||||
dirty_rows: vec![],
|
||||
deleted_rows: vec![],
|
||||
new_rows: vec![vec![json!(1), json!("2022-08-25T09:58:43Z")]],
|
||||
});
|
||||
|
||||
assert_eq!(result.validation_error, None);
|
||||
assert_eq!(
|
||||
result.statements,
|
||||
vec![
|
||||
"INSERT INTO \"APP\".\"EVENTS\" (\"ID\", \"CREATED_AT\") VALUES (1, TO_TIMESTAMP('2022-08-25 09:58:43', 'YYYY-MM-DD HH24:MI:SS'));"
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn formats_mysql_bit_literals_without_string_quotes() {
|
||||
let bit = column("flag", "bit(1)", true, None);
|
||||
|
||||
Reference in new issue
Block a user