Skip to content

Commit 0cd7c25

Browse files
peaseealamb
andauthored
feat: Add DateFieldExtractStyle::Strftime support for SqliteDialect unparser (apache#12161)
* feat: Add DateFieldExtractStyle::Strftime support for SqliteDialect (#26) * feat: Add DateFieldExtractStyle::Strftime support for SqliteDialect * refactor: Refactor DateFieldExtractStyle if checks into if/match * Fix merge issue --------- Co-authored-by: Andrew Lamb <andrew@nerdnetworks.org>
1 parent 7ca9810 commit 0cd7c25

File tree

2 files changed

+81
-21
lines changed

2 files changed

+81
-21
lines changed

datafusion/sql/src/unparser/dialect.rs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -130,6 +130,7 @@ pub enum IntervalStyle {
130130
pub enum DateFieldExtractStyle {
131131
DatePart,
132132
Extract,
133+
Strftime,
133134
}
134135

135136
pub struct DefaultDialect {}
@@ -213,6 +214,10 @@ impl Dialect for SqliteDialect {
213214
Some('`')
214215
}
215216

217+
fn date_field_extract_style(&self) -> DateFieldExtractStyle {
218+
DateFieldExtractStyle::Strftime
219+
}
220+
216221
fn date32_cast_dtype(&self) -> sqlparser::ast::DataType {
217222
sqlparser::ast::DataType::Text
218223
}

datafusion/sql/src/unparser/expr.rs

Lines changed: 76 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -524,30 +524,78 @@ impl Unparser<'_> {
524524
_func: &Arc<ScalarUDF>,
525525
args: &[Expr],
526526
) -> Option<ast::Expr> {
527-
if func_name.to_lowercase() == "date_part"
528-
&& self.dialect.date_field_extract_style() == DateFieldExtractStyle::Extract
529-
&& args.len() == 2
530-
{
531-
let date_expr = self.expr_to_sql(&args[1]).ok()?;
532-
533-
if let Expr::Literal(ScalarValue::Utf8(Some(field))) = &args[0] {
534-
let field = match field.to_lowercase().as_str() {
535-
"year" => ast::DateTimeField::Year,
536-
"month" => ast::DateTimeField::Month,
537-
"day" => ast::DateTimeField::Day,
538-
"hour" => ast::DateTimeField::Hour,
539-
"minute" => ast::DateTimeField::Minute,
540-
"second" => ast::DateTimeField::Second,
541-
_ => return None,
542-
};
527+
if func_name.to_lowercase() == "date_part" {
528+
match (self.dialect.date_field_extract_style(), args.len()) {
529+
(DateFieldExtractStyle::Extract, 2) => {
530+
let date_expr = self.expr_to_sql(&args[1]).ok()?;
531+
532+
if let Expr::Literal(ScalarValue::Utf8(Some(field))) = &args[0] {
533+
let field = match field.to_lowercase().as_str() {
534+
"year" => ast::DateTimeField::Year,
535+
"month" => ast::DateTimeField::Month,
536+
"day" => ast::DateTimeField::Day,
537+
"hour" => ast::DateTimeField::Hour,
538+
"minute" => ast::DateTimeField::Minute,
539+
"second" => ast::DateTimeField::Second,
540+
_ => return None,
541+
};
542+
543+
return Some(ast::Expr::Extract {
544+
field,
545+
expr: Box::new(date_expr),
546+
syntax: ast::ExtractSyntax::From,
547+
});
548+
}
549+
}
550+
(DateFieldExtractStyle::Strftime, 2) => {
551+
let column = self.expr_to_sql(&args[1]).ok()?;
552+
553+
if let Expr::Literal(ScalarValue::Utf8(Some(field))) = &args[0] {
554+
let field = match field.to_lowercase().as_str() {
555+
"year" => "%Y",
556+
"month" => "%m",
557+
"day" => "%d",
558+
"hour" => "%H",
559+
"minute" => "%M",
560+
"second" => "%S",
561+
_ => return None,
562+
};
543563

544-
return Some(ast::Expr::Extract {
545-
field,
546-
expr: Box::new(date_expr),
547-
syntax: ast::ExtractSyntax::From,
548-
});
564+
return Some(ast::Expr::Function(ast::Function {
565+
name: ast::ObjectName(vec![ast::Ident {
566+
value: "strftime".to_string(),
567+
quote_style: None,
568+
}]),
569+
args: ast::FunctionArguments::List(
570+
ast::FunctionArgumentList {
571+
duplicate_treatment: None,
572+
args: vec![
573+
ast::FunctionArg::Unnamed(
574+
ast::FunctionArgExpr::Expr(ast::Expr::Value(
575+
ast::Value::SingleQuotedString(
576+
field.to_string(),
577+
),
578+
)),
579+
),
580+
ast::FunctionArg::Unnamed(
581+
ast::FunctionArgExpr::Expr(column),
582+
),
583+
],
584+
clauses: vec![],
585+
},
586+
),
587+
filter: None,
588+
null_treatment: None,
589+
over: None,
590+
within_group: vec![],
591+
parameters: ast::FunctionArguments::None,
592+
}));
593+
}
594+
}
595+
_ => {} // no overrides for DateFieldExtractStyle::DatePart, because it's already a date_part
549596
}
550597
}
598+
551599
None
552600
}
553601

@@ -2178,6 +2226,7 @@ mod tests {
21782226
"YEAR",
21792227
"EXTRACT(YEAR FROM x)",
21802228
),
2229+
(DateFieldExtractStyle::Strftime, "YEAR", "strftime('%Y', x)"),
21812230
(
21822231
DateFieldExtractStyle::DatePart,
21832232
"MONTH",
@@ -2188,11 +2237,17 @@ mod tests {
21882237
"MONTH",
21892238
"EXTRACT(MONTH FROM x)",
21902239
),
2240+
(
2241+
DateFieldExtractStyle::Strftime,
2242+
"MONTH",
2243+
"strftime('%m', x)",
2244+
),
21912245
(
21922246
DateFieldExtractStyle::DatePart,
21932247
"DAY",
21942248
"date_part('DAY', x)",
21952249
),
2250+
(DateFieldExtractStyle::Strftime, "DAY", "strftime('%d', x)"),
21962251
(DateFieldExtractStyle::Extract, "DAY", "EXTRACT(DAY FROM x)"),
21972252
] {
21982253
let dialect = CustomDialectBuilder::new()

0 commit comments

Comments
 (0)