1use crate::Expr;
21use datafusion_common::{Result, plan_err};
22
23#[derive(Debug, Clone, PartialEq, Eq)]
28pub struct ArgumentName {
29 pub value: String,
31 pub is_quoted: bool,
35}
36
37pub fn resolve_function_arguments(
64 param_names: &[String],
65 args: Vec<Expr>,
66 arg_names: Vec<Option<ArgumentName>>,
67) -> Result<Vec<Expr>> {
68 if args.len() != arg_names.len() {
69 return plan_err!(
70 "Internal error: args length ({}) != arg_names length ({})",
71 args.len(),
72 arg_names.len()
73 );
74 }
75
76 if arg_names.iter().all(|name| name.is_none()) {
78 return Ok(args);
79 }
80
81 validate_argument_order(&arg_names)?;
82
83 reorder_named_arguments(param_names, args, arg_names)
84}
85
86fn validate_argument_order(arg_names: &[Option<ArgumentName>]) -> Result<()> {
88 let mut seen_named = false;
89 for (i, arg_name) in arg_names.iter().enumerate() {
90 match arg_name {
91 Some(_) => seen_named = true,
92 None if seen_named => {
93 return plan_err!(
94 "Positional argument at position {} follows named argument. \
95 All positional arguments must come before named arguments.",
96 i
97 );
98 }
99 None => {}
100 }
101 }
102 Ok(())
103}
104
105fn reorder_named_arguments(
107 param_names: &[String],
108 args: Vec<Expr>,
109 arg_names: Vec<Option<ArgumentName>>,
110) -> Result<Vec<Expr>> {
111 let positional_count = arg_names.iter().filter(|n| n.is_none()).count();
112
113 let args_len = args.len();
115
116 let expected_arg_count = param_names.len();
117
118 if positional_count > expected_arg_count {
119 return plan_err!(
120 "Too many positional arguments: expected at most {}, got {}",
121 expected_arg_count,
122 positional_count
123 );
124 }
125
126 let mut result: Vec<Option<Expr>> = vec![None; expected_arg_count];
127
128 for (i, (arg, arg_name)) in args.into_iter().zip(arg_names).enumerate() {
129 if let Some(arg_name) = arg_name {
130 let param_index = param_names
135 .iter()
136 .position(|p| {
137 if arg_name.is_quoted {
138 p == &arg_name.value
140 } else {
141 p.eq_ignore_ascii_case(&arg_name.value)
143 }
144 })
145 .ok_or_else(|| {
146 datafusion_common::plan_datafusion_err!(
147 "Unknown parameter name '{}'. Valid parameters are: [{}]",
148 arg_name.value,
149 param_names.join(", ")
150 )
151 })?;
152
153 if result[param_index].is_some() {
154 return plan_err!(
155 "Parameter '{}' specified multiple times",
156 arg_name.value
157 );
158 }
159
160 result[param_index] = Some(arg);
161 } else {
162 result[i] = Some(arg);
163 }
164 }
165
166 let required_count = args_len;
168 for i in 0..required_count {
169 if result[i].is_none() {
170 return plan_err!("Missing required parameter '{}'", param_names[i]);
171 }
172 }
173
174 Ok(result.into_iter().take(required_count).flatten().collect())
176}
177
178#[cfg(test)]
179mod tests {
180 use super::*;
181 use crate::lit;
182
183 #[test]
184 fn test_all_positional() {
185 let param_names = vec!["a".to_string(), "b".to_string()];
186
187 let args = vec![lit(1), lit("hello")];
188 let arg_names = vec![None, None];
189
190 let result =
191 resolve_function_arguments(¶m_names, args.clone(), arg_names).unwrap();
192 assert_eq!(result.len(), 2);
193 }
194
195 #[test]
196 fn test_all_named() {
197 let param_names = vec!["a".to_string(), "b".to_string()];
198
199 let args = vec![lit(1), lit("hello")];
200 let arg_names = vec![
201 Some(ArgumentName {
202 value: "a".to_string(),
203 is_quoted: false,
204 }),
205 Some(ArgumentName {
206 value: "b".to_string(),
207 is_quoted: false,
208 }),
209 ];
210
211 let result = resolve_function_arguments(¶m_names, args, arg_names).unwrap();
212 assert_eq!(result.len(), 2);
213 }
214
215 #[test]
216 fn test_case_insensitive_parameter_matching() {
217 let param_names = vec!["startpos".to_string(), "length".to_string()];
219
220 let args = vec![lit(1), lit(10)];
222 let arg_names = vec![
223 Some(ArgumentName {
224 value: "STARTPOS".to_string(),
225 is_quoted: false,
226 }),
227 Some(ArgumentName {
228 value: "LENGTH".to_string(),
229 is_quoted: false,
230 }),
231 ];
232
233 let result = resolve_function_arguments(¶m_names, args, arg_names).unwrap();
234 assert_eq!(result.len(), 2);
235 assert_eq!(result[0], lit(1));
236 assert_eq!(result[1], lit(10));
237
238 let args2 = vec![lit(20), lit(5)];
240 let arg_names2 = vec![
241 Some(ArgumentName {
242 value: "Length".to_string(),
243 is_quoted: false,
244 }),
245 Some(ArgumentName {
246 value: "StartPos".to_string(),
247 is_quoted: false,
248 }),
249 ];
250
251 let result2 =
252 resolve_function_arguments(¶m_names, args2, arg_names2).unwrap();
253 assert_eq!(result2.len(), 2);
254 assert_eq!(result2[0], lit(5)); assert_eq!(result2[1], lit(20)); }
257
258 #[test]
259 fn test_quoted_parameter_case_sensitive() {
260 let param_names = vec!["str".to_string(), "start_pos".to_string()];
262
263 let args = vec![lit("hello"), lit(1)];
265 let arg_names = vec![
266 Some(ArgumentName {
267 value: "STR".to_string(),
268 is_quoted: true,
269 }),
270 Some(ArgumentName {
271 value: "start_pos".to_string(),
272 is_quoted: true,
273 }),
274 ];
275
276 let result = resolve_function_arguments(¶m_names, args, arg_names);
277 assert!(result.is_err());
278 assert!(
279 result
280 .unwrap_err()
281 .to_string()
282 .contains("Unknown parameter")
283 );
284
285 let args2 = vec![lit("hello"), lit(1)];
287 let arg_names2 = vec![
288 Some(ArgumentName {
289 value: "str".to_string(),
290 is_quoted: true,
291 }),
292 Some(ArgumentName {
293 value: "start_pos".to_string(),
294 is_quoted: true,
295 }),
296 ];
297
298 let result2 =
299 resolve_function_arguments(¶m_names, args2, arg_names2).unwrap();
300 assert_eq!(result2.len(), 2);
301 assert_eq!(result2[0], lit("hello"));
302 assert_eq!(result2[1], lit(1));
303 }
304
305 #[test]
306 fn test_named_reordering() {
307 let param_names = vec!["a".to_string(), "b".to_string(), "c".to_string()];
308
309 let args = vec![lit(3.0), lit(1), lit("hello")];
311 let arg_names = vec![
312 Some(ArgumentName {
313 value: "c".to_string(),
314 is_quoted: false,
315 }),
316 Some(ArgumentName {
317 value: "a".to_string(),
318 is_quoted: false,
319 }),
320 Some(ArgumentName {
321 value: "b".to_string(),
322 is_quoted: false,
323 }),
324 ];
325
326 let result = resolve_function_arguments(¶m_names, args, arg_names).unwrap();
327
328 assert_eq!(result.len(), 3);
330 assert_eq!(result[0], lit(1));
331 assert_eq!(result[1], lit("hello"));
332 assert_eq!(result[2], lit(3.0));
333 }
334
335 #[test]
336 fn test_mixed_positional_and_named() {
337 let param_names = vec!["a".to_string(), "b".to_string(), "c".to_string()];
338
339 let args = vec![lit(1), lit(3.0), lit("hello")];
341 let arg_names = vec![
342 None,
343 Some(ArgumentName {
344 value: "c".to_string(),
345 is_quoted: false,
346 }),
347 Some(ArgumentName {
348 value: "b".to_string(),
349 is_quoted: false,
350 }),
351 ];
352
353 let result = resolve_function_arguments(¶m_names, args, arg_names).unwrap();
354
355 assert_eq!(result.len(), 3);
357 assert_eq!(result[0], lit(1));
358 assert_eq!(result[1], lit("hello"));
359 assert_eq!(result[2], lit(3.0));
360 }
361
362 #[test]
363 fn test_positional_after_named_error() {
364 let param_names = vec!["a".to_string(), "b".to_string()];
365
366 let args = vec![lit(1), lit("hello")];
368 let arg_names = vec![
369 Some(ArgumentName {
370 value: "a".to_string(),
371 is_quoted: false,
372 }),
373 None,
374 ];
375
376 let result = resolve_function_arguments(¶m_names, args, arg_names);
377 assert!(result.is_err());
378 assert!(
379 result
380 .unwrap_err()
381 .to_string()
382 .contains("Positional argument")
383 );
384 }
385
386 #[test]
387 fn test_unknown_parameter_name() {
388 let param_names = vec!["a".to_string(), "b".to_string()];
389
390 let args = vec![lit(1), lit("hello")];
392 let arg_names = vec![
393 Some(ArgumentName {
394 value: "x".to_string(),
395 is_quoted: false,
396 }),
397 Some(ArgumentName {
398 value: "b".to_string(),
399 is_quoted: false,
400 }),
401 ];
402
403 let result = resolve_function_arguments(¶m_names, args, arg_names);
404 assert!(result.is_err());
405 assert!(
406 result
407 .unwrap_err()
408 .to_string()
409 .contains("Unknown parameter")
410 );
411 }
412
413 #[test]
414 fn test_duplicate_parameter_name() {
415 let param_names = vec!["a".to_string(), "b".to_string()];
416
417 let args = vec![lit(1), lit(2)];
419 let arg_names = vec![
420 Some(ArgumentName {
421 value: "a".to_string(),
422 is_quoted: false,
423 }),
424 Some(ArgumentName {
425 value: "a".to_string(),
426 is_quoted: false,
427 }),
428 ];
429
430 let result = resolve_function_arguments(¶m_names, args, arg_names);
431 assert!(result.is_err());
432 assert!(
433 result
434 .unwrap_err()
435 .to_string()
436 .contains("specified multiple times")
437 );
438 }
439
440 #[test]
441 fn test_missing_required_parameter() {
442 let param_names = vec!["a".to_string(), "b".to_string(), "c".to_string()];
443
444 let args = vec![lit(1), lit(3.0)];
446 let arg_names = vec![
447 Some(ArgumentName {
448 value: "a".to_string(),
449 is_quoted: false,
450 }),
451 Some(ArgumentName {
452 value: "c".to_string(),
453 is_quoted: false,
454 }),
455 ];
456
457 let result = resolve_function_arguments(¶m_names, args, arg_names);
458 assert!(result.is_err());
459 assert!(
460 result
461 .unwrap_err()
462 .to_string()
463 .contains("Missing required parameter")
464 );
465 }
466
467 #[test]
468 fn test_mixed_case_signature_unquoted_matching() {
469 let param_names = vec![
472 "prefix".to_string(), "startPos".to_string(), "LENGTH".to_string(), ];
476
477 let args1 = vec![lit("a"), lit(1), lit(5)];
479 let arg_names1 = vec![
480 Some(ArgumentName {
481 value: "prefix".to_string(),
482 is_quoted: false,
483 }),
484 Some(ArgumentName {
485 value: "startpos".to_string(), is_quoted: false,
487 }),
488 Some(ArgumentName {
489 value: "length".to_string(), is_quoted: false,
491 }),
492 ];
493
494 let result1 =
495 resolve_function_arguments(¶m_names, args1, arg_names1).unwrap();
496 assert_eq!(result1.len(), 3);
497 assert_eq!(result1[0], lit("a"));
498 assert_eq!(result1[1], lit(1));
499 assert_eq!(result1[2], lit(5));
500
501 let args2 = vec![lit("b"), lit(2), lit(10)];
503 let arg_names2 = vec![
504 Some(ArgumentName {
505 value: "PREFIX".to_string(), is_quoted: false,
507 }),
508 Some(ArgumentName {
509 value: "STARTPOS".to_string(), is_quoted: false,
511 }),
512 Some(ArgumentName {
513 value: "LENGTH".to_string(), is_quoted: false,
515 }),
516 ];
517
518 let result2 =
519 resolve_function_arguments(¶m_names, args2, arg_names2).unwrap();
520 assert_eq!(result2.len(), 3);
521 assert_eq!(result2[0], lit("b"));
522 assert_eq!(result2[1], lit(2));
523 assert_eq!(result2[2], lit(10));
524
525 let args3 = vec![lit("c"), lit(3), lit(15)];
527 let arg_names3 = vec![
528 Some(ArgumentName {
529 value: "Prefix".to_string(), is_quoted: false,
531 }),
532 Some(ArgumentName {
533 value: "StartPos".to_string(), is_quoted: false,
535 }),
536 Some(ArgumentName {
537 value: "Length".to_string(), is_quoted: false,
539 }),
540 ];
541
542 let result3 =
543 resolve_function_arguments(¶m_names, args3, arg_names3).unwrap();
544 assert_eq!(result3.len(), 3);
545 assert_eq!(result3[0], lit("c"));
546 assert_eq!(result3[1], lit(3));
547 assert_eq!(result3[2], lit(15));
548 }
549
550 #[test]
551 fn test_mixed_case_signature_quoted_matching() {
552 let param_names = vec![
554 "prefix".to_string(), "startPos".to_string(), "LENGTH".to_string(), ];
558
559 let args_wrong_prefix = vec![lit("a"), lit(1), lit(5)];
561 let arg_names_wrong_prefix = vec![
562 Some(ArgumentName {
563 value: "PREFIX".to_string(), is_quoted: true,
565 }),
566 Some(ArgumentName {
567 value: "startPos".to_string(),
568 is_quoted: true,
569 }),
570 Some(ArgumentName {
571 value: "LENGTH".to_string(),
572 is_quoted: true,
573 }),
574 ];
575
576 let result = resolve_function_arguments(
577 ¶m_names,
578 args_wrong_prefix,
579 arg_names_wrong_prefix,
580 );
581 assert!(result.is_err());
582 assert!(
583 result
584 .unwrap_err()
585 .to_string()
586 .contains("Unknown parameter")
587 );
588
589 let args_wrong_startpos = vec![lit("a"), lit(1), lit(5)];
591 let arg_names_wrong_startpos = vec![
592 Some(ArgumentName {
593 value: "prefix".to_string(),
594 is_quoted: true,
595 }),
596 Some(ArgumentName {
597 value: "STARTPOS".to_string(), is_quoted: true,
599 }),
600 Some(ArgumentName {
601 value: "LENGTH".to_string(),
602 is_quoted: true,
603 }),
604 ];
605
606 let result2 = resolve_function_arguments(
607 ¶m_names,
608 args_wrong_startpos,
609 arg_names_wrong_startpos,
610 );
611 assert!(result2.is_err());
612 assert!(
613 result2
614 .unwrap_err()
615 .to_string()
616 .contains("Unknown parameter")
617 );
618
619 let args_wrong_length = vec![lit("a"), lit(1), lit(5)];
621 let arg_names_wrong_length = vec![
622 Some(ArgumentName {
623 value: "prefix".to_string(),
624 is_quoted: true,
625 }),
626 Some(ArgumentName {
627 value: "startPos".to_string(),
628 is_quoted: true,
629 }),
630 Some(ArgumentName {
631 value: "length".to_string(), is_quoted: true,
633 }),
634 ];
635
636 let result3 = resolve_function_arguments(
637 ¶m_names,
638 args_wrong_length,
639 arg_names_wrong_length,
640 );
641 assert!(result3.is_err());
642 assert!(
643 result3
644 .unwrap_err()
645 .to_string()
646 .contains("Unknown parameter")
647 );
648
649 let args_correct = vec![lit("a"), lit(1), lit(5)];
651 let arg_names_correct = vec![
652 Some(ArgumentName {
653 value: "prefix".to_string(), is_quoted: true,
655 }),
656 Some(ArgumentName {
657 value: "startPos".to_string(), is_quoted: true,
659 }),
660 Some(ArgumentName {
661 value: "LENGTH".to_string(), is_quoted: true,
663 }),
664 ];
665
666 let result4 =
667 resolve_function_arguments(¶m_names, args_correct, arg_names_correct)
668 .unwrap();
669 assert_eq!(result4.len(), 3);
670 assert_eq!(result4[0], lit("a"));
671 assert_eq!(result4[1], lit(1));
672 assert_eq!(result4[2], lit(5));
673 }
674}