query/optimizer/
insert_assignment.rs1use std::sync::Arc;
16
17use common_time::Timezone;
18use datafusion::config::ConfigOptions;
19use datafusion_common::{DFSchemaRef, Result, ScalarValue};
20use datafusion_expr::expr::{Alias, Cast};
21use datafusion_expr::{Distinct, Expr, ExprSchemable, LogicalPlan, Projection, Values};
22use datafusion_optimizer::analyzer::AnalyzerRule;
23use datafusion_optimizer::analyzer::type_coercion::TypeCoercion;
24use datatypes::arrow::datatypes::{DataType, TimeUnit};
25use session::context::QueryContextRef;
26
27use crate::optimizer::type_conversion::cast_string_to_timestamp;
28
29pub(crate) fn rewrite_insert_assignments(
75 plan: LogicalPlan,
76 query_ctx: &QueryContextRef,
77 config: &ConfigOptions,
78) -> Result<LogicalPlan> {
79 let Some(timezone) = session_timezone(query_ctx) else {
80 return Ok(plan);
81 };
82
83 let plan = TypeCoercion::new().analyze(plan, config)?;
84 rewrite_assignment(plan, &timezone)
85}
86
87struct SessionTimezone {
89 parsed: Timezone,
91 name: Arc<str>,
93}
94
95fn session_timezone(query_ctx: &QueryContextRef) -> Option<SessionTimezone> {
96 let parsed = query_ctx.timezone();
97
98 if parsed.is_utc() {
100 return None;
101 }
102
103 Some(SessionTimezone {
104 name: Arc::from(parsed.to_string()),
105 parsed,
106 })
107}
108
109fn rewrite_assignment(plan: LogicalPlan, timezone: &SessionTimezone) -> Result<LogicalPlan> {
110 let LogicalPlan::Projection(assignment) = plan else {
111 return Ok(plan);
112 };
113
114 let mut exprs = assignment.expr.clone();
115 let mut changed = false;
116 for expr in &mut exprs {
117 changed |= retarget_assignment_cast(
118 expr,
119 assignment.input.schema(),
120 Some(assignment.input.as_ref()),
121 timezone,
122 )?;
123 }
124
125 let mut input = assignment.input.clone();
128 if let LogicalPlan::Values(values) = assignment.input.as_ref()
129 && let Some(rewritten) = rewrite_values(values, timezone)?
130 {
131 input = Arc::new(LogicalPlan::Values(rewritten));
132 changed = true;
133 }
134
135 if !changed {
136 return Ok(LogicalPlan::Projection(assignment));
137 }
138 Projection::try_new(exprs, input).map(LogicalPlan::Projection)
139}
140
141fn rewrite_values(values: &Values, timezone: &SessionTimezone) -> Result<Option<Values>> {
142 let mut rewritten = values.clone();
143 let mut changed = false;
144 for row in &mut rewritten.values {
145 for expr in row.iter_mut() {
146 changed |= retarget_assignment_cast(expr, &values.schema, None, timezone)?;
147 }
148 }
149
150 Ok(changed.then_some(rewritten))
151}
152
153fn retarget_assignment_cast(
158 expr: &mut Expr,
159 schema: &DFSchemaRef,
160 source_plan: Option<&LogicalPlan>,
161 timezone: &SessionTimezone,
162) -> Result<bool> {
163 let expr = unalias_mut(expr);
164 let Expr::Cast(Cast {
165 expr: source,
166 field,
167 }) = expr
168 else {
169 return Ok(false);
170 };
171 let DataType::Timestamp(unit, None) = field.data_type() else {
172 return Ok(false);
173 };
174 let unit = *unit;
175
176 if !matches!(
177 source.get_type(schema)?,
178 DataType::Utf8 | DataType::LargeUtf8 | DataType::Utf8View
179 ) {
180 return Ok(false);
181 }
182
183 let folded = source_literal(source.as_ref(), source_plan)
188 .and_then(|literal| convert_literal(&literal, unit, &timezone.parsed));
189 if let Some(folded) = folded {
190 *expr = folded;
191 return Ok(true);
192 }
193
194 let source = source.as_ref().clone();
195 *expr = Expr::Cast(Cast::new(
196 Box::new(Expr::Cast(Cast::new(
197 Box::new(source),
198 DataType::Timestamp(unit, Some(timezone.name.clone())),
199 ))),
200 DataType::Timestamp(unit, None),
201 ));
202 Ok(true)
203}
204
205fn source_literal(source: &Expr, source_plan: Option<&LogicalPlan>) -> Option<ScalarValue> {
206 match source {
207 Expr::Literal(value, _) => Some(value.clone()),
208 Expr::Column(column) => {
209 let plan = source_plan?;
210 let index = plan.schema().maybe_index_of_column(column)?;
211 lineage_literal(plan, index).cloned()
212 }
213 _ => None,
214 }
215}
216
217fn lineage_literal(plan: &LogicalPlan, output_idx: usize) -> Option<&ScalarValue> {
224 if output_idx >= plan.schema().fields().len() {
225 return None;
226 }
227
228 match plan {
229 LogicalPlan::Projection(projection) => match unalias(&projection.expr[output_idx]) {
230 Expr::Literal(value, _) => Some(value),
231 Expr::Column(column) => {
232 let input_idx = projection.input.schema().maybe_index_of_column(column)?;
233 lineage_literal(projection.input.as_ref(), input_idx)
234 }
235 _ => None,
236 },
237 LogicalPlan::Filter(_)
238 | LogicalPlan::Sort(_)
239 | LogicalPlan::Limit(_)
240 | LogicalPlan::SubqueryAlias(_)
241 | LogicalPlan::Distinct(Distinct::All(_)) => {
242 let inputs = plan.inputs();
243 let [input] = inputs.as_slice() else {
244 return None;
245 };
246 lineage_literal(input, output_idx)
247 }
248 _ => None,
249 }
250}
251
252fn convert_literal(value: &ScalarValue, unit: TimeUnit, timezone: &Timezone) -> Option<Expr> {
253 let ScalarValue::Utf8(Some(value)) = value else {
254 return None;
255 };
256 cast_string_to_timestamp(value, &DataType::Timestamp(unit, None), Some(timezone))
257 .ok()
258 .filter(|value| !value.is_null())
259 .map(|value| Expr::Literal(value, None))
260}
261
262fn unalias(expr: &Expr) -> &Expr {
263 match expr {
264 Expr::Alias(Alias { expr, .. }) => unalias(expr),
265 expr => expr,
266 }
267}
268
269fn unalias_mut(expr: &mut Expr) -> &mut Expr {
270 match expr {
271 Expr::Alias(Alias { expr, .. }) => unalias_mut(expr),
272 expr => expr,
273 }
274}
275
276#[cfg(test)]
277mod tests {
278 use datafusion_common::DFSchema;
279 use datafusion_expr::expr::Placeholder;
280
281 use super::*;
282
283 fn shanghai() -> SessionTimezone {
284 let parsed = Timezone::from_tz_string("Asia/Shanghai").unwrap();
285 SessionTimezone {
286 name: Arc::from(parsed.to_string()),
287 parsed,
288 }
289 }
290
291 #[test]
294 fn test_untyped_placeholder_assignment_is_left_alone() {
295 let schema = Arc::new(DFSchema::empty());
296 let mut expr = Expr::Cast(Cast::new(
297 Box::new(Expr::Placeholder(Placeholder::new_with_field(
298 "$1".to_string(),
299 None,
300 ))),
301 DataType::Timestamp(TimeUnit::Millisecond, None),
302 ));
303 let original = expr.clone();
304
305 let changed = retarget_assignment_cast(&mut expr, &schema, None, &shanghai()).unwrap();
306
307 assert!(!changed);
308 assert_eq!(expr, original);
309 }
310}