1use anyhow::{anyhow, Result};
2use chrono::{DateTime, Datelike, NaiveDate, NaiveDateTime, TimeZone, Utc};
3
4use super::{ArgCount, FunctionCategory, FunctionSignature, SqlFunction};
5use crate::config::global::get_date_notation;
6use crate::data::datatable::DataValue;
7
8pub fn parse_datetime(s: &str) -> Result<DateTime<Utc>> {
10 if let Ok(dt) = s.parse::<DateTime<Utc>>() {
12 return Ok(dt);
13 }
14
15 if let Ok(dt) = NaiveDateTime::parse_from_str(s, "%Y%m%d-%H:%M:%S%.3f") {
18 return Ok(Utc.from_utc_datetime(&dt));
19 }
20 if let Ok(dt) = NaiveDateTime::parse_from_str(s, "%Y%m%d-%H:%M:%S") {
22 return Ok(Utc.from_utc_datetime(&dt));
23 }
24
25 if let Ok(dt) = NaiveDateTime::parse_from_str(s, "%Y-%m-%dT%H:%M:%S") {
28 return Ok(Utc.from_utc_datetime(&dt));
29 }
30 if let Ok(dt) = NaiveDateTime::parse_from_str(s, "%Y-%m-%dT%H:%M:%S%.3f") {
31 return Ok(Utc.from_utc_datetime(&dt));
32 }
33 if let Ok(dt) = NaiveDateTime::parse_from_str(s, "%Y-%m-%d %H:%M:%S%.3f") {
35 return Ok(Utc.from_utc_datetime(&dt));
36 }
37 if let Ok(dt) = NaiveDateTime::parse_from_str(s, "%Y-%m-%d %H:%M:%S%.f") {
38 return Ok(Utc.from_utc_datetime(&dt));
39 }
40 if let Ok(dt) = NaiveDateTime::parse_from_str(s, "%Y-%m-%d %H:%M:%S") {
42 return Ok(Utc.from_utc_datetime(&dt));
43 }
44 if let Ok(dt) = NaiveDate::parse_from_str(s, "%Y-%m-%d") {
45 return Ok(Utc.from_utc_datetime(&dt.and_hms_opt(0, 0, 0).unwrap()));
46 }
47
48 let date_notation = get_date_notation();
50
51 if date_notation == "european" {
53 if let Ok(dt) = NaiveDate::parse_from_str(s, "%d/%m/%Y") {
56 return Ok(Utc.from_utc_datetime(&dt.and_hms_opt(0, 0, 0).unwrap()));
57 }
58 if let Ok(dt) = NaiveDate::parse_from_str(s, "%d-%m-%Y") {
59 return Ok(Utc.from_utc_datetime(&dt.and_hms_opt(0, 0, 0).unwrap()));
60 }
61 if let Ok(dt) = NaiveDateTime::parse_from_str(s, "%d/%m/%Y %H:%M:%S%.3f") {
63 return Ok(Utc.from_utc_datetime(&dt));
64 }
65 if let Ok(dt) = NaiveDateTime::parse_from_str(s, "%d/%m/%Y %H:%M:%S") {
66 return Ok(Utc.from_utc_datetime(&dt));
67 }
68 if let Ok(dt) = NaiveDateTime::parse_from_str(s, "%d-%m-%Y %H:%M:%S") {
69 return Ok(Utc.from_utc_datetime(&dt));
70 }
71
72 if let Ok(dt) = NaiveDate::parse_from_str(s, "%m/%d/%Y") {
75 return Ok(Utc.from_utc_datetime(&dt.and_hms_opt(0, 0, 0).unwrap()));
76 }
77 if let Ok(dt) = NaiveDate::parse_from_str(s, "%m-%d-%Y") {
78 return Ok(Utc.from_utc_datetime(&dt.and_hms_opt(0, 0, 0).unwrap()));
79 }
80 if let Ok(dt) = NaiveDateTime::parse_from_str(s, "%m/%d/%Y %H:%M:%S%.3f") {
82 return Ok(Utc.from_utc_datetime(&dt));
83 }
84 if let Ok(dt) = NaiveDateTime::parse_from_str(s, "%m/%d/%Y %H:%M:%S") {
85 return Ok(Utc.from_utc_datetime(&dt));
86 }
87 if let Ok(dt) = NaiveDateTime::parse_from_str(s, "%m-%d-%Y %H:%M:%S") {
88 return Ok(Utc.from_utc_datetime(&dt));
89 }
90 } else {
91 if let Ok(dt) = NaiveDate::parse_from_str(s, "%m/%d/%Y") {
94 return Ok(Utc.from_utc_datetime(&dt.and_hms_opt(0, 0, 0).unwrap()));
95 }
96 if let Ok(dt) = NaiveDate::parse_from_str(s, "%m-%d-%Y") {
97 return Ok(Utc.from_utc_datetime(&dt.and_hms_opt(0, 0, 0).unwrap()));
98 }
99 if let Ok(dt) = NaiveDateTime::parse_from_str(s, "%m/%d/%Y %H:%M:%S%.3f") {
101 return Ok(Utc.from_utc_datetime(&dt));
102 }
103 if let Ok(dt) = NaiveDateTime::parse_from_str(s, "%m/%d/%Y %H:%M:%S") {
104 return Ok(Utc.from_utc_datetime(&dt));
105 }
106 if let Ok(dt) = NaiveDateTime::parse_from_str(s, "%m-%d-%Y %H:%M:%S") {
107 return Ok(Utc.from_utc_datetime(&dt));
108 }
109
110 if let Ok(dt) = NaiveDate::parse_from_str(s, "%d/%m/%Y") {
113 return Ok(Utc.from_utc_datetime(&dt.and_hms_opt(0, 0, 0).unwrap()));
114 }
115 if let Ok(dt) = NaiveDate::parse_from_str(s, "%d-%m-%Y") {
116 return Ok(Utc.from_utc_datetime(&dt.and_hms_opt(0, 0, 0).unwrap()));
117 }
118 if let Ok(dt) = NaiveDateTime::parse_from_str(s, "%d/%m/%Y %H:%M:%S%.3f") {
120 return Ok(Utc.from_utc_datetime(&dt));
121 }
122 if let Ok(dt) = NaiveDateTime::parse_from_str(s, "%d/%m/%Y %H:%M:%S") {
123 return Ok(Utc.from_utc_datetime(&dt));
124 }
125 if let Ok(dt) = NaiveDateTime::parse_from_str(s, "%d-%m-%Y %H:%M:%S") {
126 return Ok(Utc.from_utc_datetime(&dt));
127 }
128 }
129
130 if let Ok(dt) = NaiveDate::parse_from_str(s, "%d-%b-%Y") {
132 return Ok(Utc.from_utc_datetime(&dt.and_hms_opt(0, 0, 0).unwrap()));
133 }
134
135 if let Ok(dt) = NaiveDate::parse_from_str(s, "%B %d, %Y") {
137 return Ok(Utc.from_utc_datetime(&dt.and_hms_opt(0, 0, 0).unwrap()));
138 }
139 if let Ok(dt) = NaiveDate::parse_from_str(s, "%d %B %Y") {
140 return Ok(Utc.from_utc_datetime(&dt.and_hms_opt(0, 0, 0).unwrap()));
141 }
142
143 if let Ok(dt) = DateTime::parse_from_rfc3339(s) {
145 return Ok(dt.with_timezone(&Utc));
146 }
147
148 Err(anyhow!("Could not parse date: {}. Supported formats: YYYYMMDD-HH:MM:SS.sss (FIX), YYYY-MM-DD, MM/DD/YYYY, DD/MM/YYYY, DD-MMM-YYYY, Month DD YYYY", s))
149}
150
151pub struct NowFunction;
153
154impl SqlFunction for NowFunction {
155 fn signature(&self) -> FunctionSignature {
156 FunctionSignature {
157 name: "NOW",
158 category: FunctionCategory::Date,
159 arg_count: ArgCount::Fixed(0),
160 description: "Returns the current date and time",
161 returns: "DATETIME",
162 examples: vec![
163 "SELECT NOW()",
164 "SELECT * FROM orders WHERE created_at > NOW() - 7",
165 ],
166 }
167 }
168
169 fn evaluate(&self, args: &[DataValue]) -> Result<DataValue> {
170 self.validate_args(args)?;
171 let now = Utc::now();
172 Ok(DataValue::DateTime(
173 now.format("%Y-%m-%d %H:%M:%S").to_string(),
174 ))
175 }
176}
177
178pub struct TodayFunction;
180
181impl SqlFunction for TodayFunction {
182 fn signature(&self) -> FunctionSignature {
183 FunctionSignature {
184 name: "TODAY",
185 category: FunctionCategory::Date,
186 arg_count: ArgCount::Fixed(0),
187 description: "Returns today's date",
188 returns: "DATE",
189 examples: vec![
190 "SELECT TODAY()",
191 "SELECT * FROM events WHERE event_date = TODAY()",
192 ],
193 }
194 }
195
196 fn evaluate(&self, args: &[DataValue]) -> Result<DataValue> {
197 self.validate_args(args)?;
198 let today = Utc::now().date_naive();
199 Ok(DataValue::String(today.format("%Y-%m-%d").to_string()))
200 }
201}
202
203pub struct DateDiffFunction;
205
206impl SqlFunction for DateDiffFunction {
207 fn signature(&self) -> FunctionSignature {
208 FunctionSignature {
209 name: "DATEDIFF",
210 category: FunctionCategory::Date,
211 arg_count: ArgCount::Fixed(3),
212 description: "Calculate the difference between two dates in the specified unit",
213 returns: "INTEGER",
214 examples: vec![
215 "SELECT DATEDIFF('day', '2024-01-01', '2024-01-15')",
216 "SELECT DATEDIFF('month', start_date, end_date) FROM projects",
217 "SELECT DATEDIFF('year', birth_date, TODAY()) as age",
218 ],
219 }
220 }
221
222 fn evaluate(&self, args: &[DataValue]) -> Result<DataValue> {
223 self.validate_args(args)?;
224
225 let unit = match &args[0] {
227 DataValue::String(s) => s.to_lowercase(),
228 DataValue::InternedString(s) => s.to_lowercase(),
229 _ => return Err(anyhow!("DATEDIFF unit must be a string")),
230 };
231
232 let date1 = match &args[1] {
234 DataValue::String(s) | DataValue::DateTime(s) => parse_datetime(s)?,
235 DataValue::InternedString(s) => parse_datetime(s.as_str())?,
236 DataValue::Null => return Ok(DataValue::Null),
237 _ => return Err(anyhow!("DATEDIFF requires date/datetime values")),
238 };
239
240 let date2 = match &args[2] {
242 DataValue::String(s) | DataValue::DateTime(s) => parse_datetime(s)?,
243 DataValue::InternedString(s) => parse_datetime(s.as_str())?,
244 DataValue::Null => return Ok(DataValue::Null),
245 _ => return Err(anyhow!("DATEDIFF requires date/datetime values")),
246 };
247
248 let diff = match unit.as_str() {
250 "day" | "days" => {
251 let duration = date2.signed_duration_since(date1);
252 duration.num_days()
253 }
254 "month" | "months" => {
255 let duration = date2.signed_duration_since(date1);
257 duration.num_days() / 30
258 }
259 "year" | "years" => {
260 let duration = date2.signed_duration_since(date1);
262 duration.num_days() / 365
263 }
264 "hour" | "hours" => {
265 let duration = date2.signed_duration_since(date1);
266 duration.num_hours()
267 }
268 "minute" | "minutes" => {
269 let duration = date2.signed_duration_since(date1);
270 duration.num_minutes()
271 }
272 "second" | "seconds" => {
273 let duration = date2.signed_duration_since(date1);
274 duration.num_seconds()
275 }
276 "millisecond" | "milliseconds" | "ms" => {
277 let duration = date2.signed_duration_since(date1);
278 duration.num_milliseconds()
279 }
280 "microsecond" | "microseconds" | "us" => {
281 let duration = date2.signed_duration_since(date1);
282 duration.num_microseconds().unwrap_or(0)
283 }
284 _ => {
285 return Err(anyhow!(
286 "Unknown DATEDIFF unit: {}. Use: day, month, year, hour, minute, second, millisecond, microsecond",
287 unit
288 ))
289 }
290 };
291
292 Ok(DataValue::Integer(diff))
293 }
294}
295
296pub struct DateAddFunction;
298
299impl SqlFunction for DateAddFunction {
300 fn signature(&self) -> FunctionSignature {
301 FunctionSignature {
302 name: "DATEADD",
303 category: FunctionCategory::Date,
304 arg_count: ArgCount::Fixed(3),
305 description: "Add a specified interval to a date",
306 returns: "DATETIME",
307 examples: vec![
308 "SELECT DATEADD('day', 7, '2024-01-01')",
309 "SELECT DATEADD('month', -1, NOW())",
310 "SELECT DATEADD('year', 1, hire_date) FROM employees",
311 ],
312 }
313 }
314
315 fn evaluate(&self, args: &[DataValue]) -> Result<DataValue> {
316 self.validate_args(args)?;
317
318 let unit = match &args[0] {
320 DataValue::String(s) => s.to_lowercase(),
321 DataValue::InternedString(s) => s.to_lowercase(),
322 _ => return Err(anyhow!("DATEADD unit must be a string")),
323 };
324
325 let amount = match &args[1] {
327 DataValue::Integer(i) => *i,
328 DataValue::Float(f) => *f as i64,
329 DataValue::Null => return Ok(DataValue::Null),
330 _ => return Err(anyhow!("DATEADD amount must be a number")),
331 };
332
333 let base_date = match &args[2] {
335 DataValue::String(s) | DataValue::DateTime(s) => parse_datetime(s)?,
336 DataValue::InternedString(s) => parse_datetime(s.as_str())?,
337 DataValue::Null => return Ok(DataValue::Null),
338 _ => return Err(anyhow!("DATEADD requires date/datetime values")),
339 };
340
341 let result_date = match unit.as_str() {
343 "day" | "days" => base_date + chrono::Duration::days(amount),
344 "month" | "months" => {
345 let naive = base_date.naive_utc();
347 let mut year = naive.year();
348 let mut month = naive.month() as i32;
349 let day = naive.day();
350
351 month += amount as i32;
352
353 while month > 12 {
355 month -= 12;
356 year += 1;
357 }
358 while month < 1 {
359 month += 12;
360 year -= 1;
361 }
362
363 let target_date =
365 NaiveDate::from_ymd_opt(year, month as u32, day).unwrap_or_else(|| {
366 for test_day in (1..=day).rev() {
369 if let Some(date) =
370 NaiveDate::from_ymd_opt(year, month as u32, test_day)
371 {
372 return date;
373 }
374 }
375 NaiveDate::from_ymd_opt(year, month as u32, 1).unwrap()
376 });
377
378 Utc.from_utc_datetime(&target_date.and_time(base_date.naive_utc().time()))
379 }
380 "year" | "years" => {
381 let naive = base_date.naive_utc();
382 let new_year = naive.year() + amount as i32;
383 let month = naive.month();
384 let day = naive.day();
385
386 let target_date =
388 NaiveDate::from_ymd_opt(new_year, month, day).unwrap_or_else(|| {
389 NaiveDate::from_ymd_opt(new_year, month, day - 1).unwrap()
391 });
392
393 Utc.from_utc_datetime(&target_date.and_time(base_date.naive_utc().time()))
394 }
395 "hour" | "hours" => base_date + chrono::Duration::hours(amount),
396 "minute" | "minutes" => base_date + chrono::Duration::minutes(amount),
397 "second" | "seconds" => base_date + chrono::Duration::seconds(amount),
398 _ => {
399 return Err(anyhow!(
400 "Unknown DATEADD unit: {}. Use: day, month, year, hour, minute, second",
401 unit
402 ))
403 }
404 };
405
406 Ok(DataValue::DateTime(
408 result_date.format("%Y-%m-%d %H:%M:%S").to_string(),
409 ))
410 }
411}
412
413pub struct UnixTimestamp;
415
416impl SqlFunction for UnixTimestamp {
417 fn signature(&self) -> FunctionSignature {
418 FunctionSignature {
419 name: "UNIX_TIMESTAMP",
420 category: FunctionCategory::Date,
421 arg_count: ArgCount::Fixed(1),
422 description:
423 "Convert datetime to Unix epoch timestamp (seconds since 1970-01-01 00:00:00 UTC)",
424 returns: "INTEGER (seconds since epoch)",
425 examples: vec![
426 "SELECT UNIX_TIMESTAMP('2024-01-01 00:00:00')",
427 "SELECT UNIX_TIMESTAMP('2024-01-01T12:30:45')",
428 "SELECT UNIX_TIMESTAMP(trade_time) FROM trades",
429 ],
430 }
431 }
432
433 fn evaluate(&self, args: &[DataValue]) -> Result<DataValue> {
434 if args.len() != 1 {
435 return Err(anyhow!("UNIX_TIMESTAMP expects exactly 1 argument"));
436 }
437
438 match &args[0] {
439 DataValue::DateTime(dt_str) | DataValue::String(dt_str) => {
440 let dt = parse_datetime(dt_str)?;
441 Ok(DataValue::Integer(dt.timestamp()))
442 }
443 DataValue::InternedString(dt_str) => {
444 let dt = parse_datetime(dt_str)?;
445 Ok(DataValue::Integer(dt.timestamp()))
446 }
447 DataValue::Null => Ok(DataValue::Null),
448 _ => Err(anyhow!(
449 "UNIX_TIMESTAMP expects a datetime or string argument"
450 )),
451 }
452 }
453}
454
455pub struct FromUnixTime;
457
458impl SqlFunction for FromUnixTime {
459 fn signature(&self) -> FunctionSignature {
460 FunctionSignature {
461 name: "FROM_UNIXTIME",
462 category: FunctionCategory::Date,
463 arg_count: ArgCount::Fixed(1),
464 description: "Convert Unix epoch timestamp to datetime string",
465 returns: "DATETIME string in ISO format",
466 examples: vec![
467 "SELECT FROM_UNIXTIME(1704067200)",
468 "SELECT FROM_UNIXTIME(timestamp_col) FROM data",
469 ],
470 }
471 }
472
473 fn evaluate(&self, args: &[DataValue]) -> Result<DataValue> {
474 if args.len() != 1 {
475 return Err(anyhow!("FROM_UNIXTIME expects exactly 1 argument"));
476 }
477
478 match &args[0] {
479 DataValue::Integer(timestamp) => {
480 let dt = DateTime::<Utc>::from_timestamp(*timestamp, 0)
481 .ok_or_else(|| anyhow!("Invalid timestamp: {}", timestamp))?;
482 Ok(DataValue::DateTime(
483 dt.format("%Y-%m-%d %H:%M:%S").to_string(),
484 ))
485 }
486 DataValue::Float(timestamp) => {
487 let secs = timestamp.floor() as i64;
488 let nanos = ((timestamp - timestamp.floor()) * 1_000_000_000.0) as u32;
489 let dt = DateTime::<Utc>::from_timestamp(secs, nanos)
490 .ok_or_else(|| anyhow!("Invalid timestamp: {}", timestamp))?;
491 Ok(DataValue::DateTime(
492 dt.format("%Y-%m-%d %H:%M:%S%.f").to_string(),
493 ))
494 }
495 DataValue::Null => Ok(DataValue::Null),
496 _ => Err(anyhow!("FROM_UNIXTIME expects a numeric timestamp")),
497 }
498 }
499}
500
501pub struct TimeBucket;
503
504impl SqlFunction for TimeBucket {
505 fn signature(&self) -> FunctionSignature {
506 FunctionSignature {
507 name: "TIME_BUCKET",
508 category: FunctionCategory::Date,
509 arg_count: ArgCount::Fixed(2),
510 description: "Round timestamp down to bucket boundary (for time-based grouping)",
511 returns: "INTEGER (bucket timestamp)",
512 examples: vec![
513 "SELECT TIME_BUCKET(300, UNIX_TIMESTAMP(trade_time)) as bucket FROM trades -- 5 minute buckets",
514 "SELECT TIME_BUCKET(3600, UNIX_TIMESTAMP(trade_time)) as hour FROM trades -- 1 hour buckets",
515 "SELECT TIME_BUCKET(60, timestamp_col) as minute FROM data -- 1 minute buckets",
516 ],
517 }
518 }
519
520 fn evaluate(&self, args: &[DataValue]) -> Result<DataValue> {
521 if args.len() != 2 {
522 return Err(anyhow!(
523 "TIME_BUCKET expects exactly 2 arguments: bucket_size, timestamp"
524 ));
525 }
526
527 let bucket_size = match &args[0] {
528 DataValue::Integer(size) => *size,
529 DataValue::Float(size) => *size as i64,
530 _ => return Err(anyhow!("TIME_BUCKET bucket_size must be numeric")),
531 };
532
533 if bucket_size <= 0 {
534 return Err(anyhow!("TIME_BUCKET bucket_size must be positive"));
535 }
536
537 match &args[1] {
538 DataValue::Integer(timestamp) => {
539 let bucket = (timestamp / bucket_size) * bucket_size;
540 Ok(DataValue::Integer(bucket))
541 }
542 DataValue::Float(timestamp) => {
543 let ts = *timestamp as i64;
544 let bucket = (ts / bucket_size) * bucket_size;
545 Ok(DataValue::Integer(bucket))
546 }
547 DataValue::Null => Ok(DataValue::Null),
548 _ => Err(anyhow!("TIME_BUCKET timestamp must be numeric")),
549 }
550 }
551}
552
553pub struct DayOfWeekFunction;
555
556impl SqlFunction for DayOfWeekFunction {
557 fn signature(&self) -> FunctionSignature {
558 FunctionSignature {
559 name: "DAYOFWEEK",
560 category: FunctionCategory::Date,
561 arg_count: ArgCount::Fixed(1),
562 description: "Returns day of week as number (0=Sunday, 6=Saturday)",
563 returns: "INTEGER",
564 examples: vec![
565 "SELECT DAYOFWEEK('2024-01-01')", "SELECT DAYOFWEEK(NOW())",
567 "SELECT DAYOFWEEK(date_column) FROM table",
568 ],
569 }
570 }
571
572 fn evaluate(&self, args: &[DataValue]) -> Result<DataValue> {
573 if args.len() != 1 {
574 return Err(anyhow!("DAYOFWEEK expects exactly 1 argument"));
575 }
576
577 let date_str = match &args[0] {
578 DataValue::String(s) | DataValue::DateTime(s) => s.as_str(),
579 DataValue::InternedString(s) => s.as_str(),
580 DataValue::Null => return Ok(DataValue::Null),
581 _ => return Err(anyhow!("DAYOFWEEK expects a date/datetime string")),
582 };
583
584 let dt = parse_datetime(date_str)?;
585 let chrono_weekday = dt.weekday().num_days_from_monday(); let our_weekday = (chrono_weekday + 1) % 7; Ok(DataValue::Integer(our_weekday as i64))
591 }
592}
593
594pub struct DayNameFunction;
596
597impl SqlFunction for DayNameFunction {
598 fn signature(&self) -> FunctionSignature {
599 FunctionSignature {
600 name: "DAYNAME",
601 category: FunctionCategory::Date,
602 arg_count: ArgCount::Range(1, 2),
603 description: "Returns day name. Optional second arg: 'full' (default) or 'short'",
604 returns: "STRING",
605 examples: vec![
606 "SELECT DAYNAME('2024-01-01')", "SELECT DAYNAME('2024-01-01', 'short')", "SELECT DAYNAME(NOW(), 'full')",
609 ],
610 }
611 }
612
613 fn evaluate(&self, args: &[DataValue]) -> Result<DataValue> {
614 if args.is_empty() || args.len() > 2 {
615 return Err(anyhow!("DAYNAME expects 1 or 2 arguments"));
616 }
617
618 let date_str = match &args[0] {
619 DataValue::String(s) | DataValue::DateTime(s) => s.as_str(),
620 DataValue::InternedString(s) => s.as_str(),
621 DataValue::Null => return Ok(DataValue::Null),
622 _ => return Err(anyhow!("DAYNAME expects a date/datetime string")),
623 };
624
625 let format = if args.len() == 2 {
626 match &args[1] {
627 DataValue::String(s) => s.as_str(),
628 DataValue::InternedString(s) => s.as_str(),
629 DataValue::Null => "full",
630 _ => return Err(anyhow!("DAYNAME format must be 'full' or 'short'")),
631 }
632 } else {
633 "full"
634 };
635
636 let dt = parse_datetime(date_str)?;
637
638 let day_name = match format {
639 "short" => dt.format("%a").to_string(), "full" | _ => dt.format("%A").to_string(), };
642
643 Ok(DataValue::String(day_name))
644 }
645}
646
647pub struct IsLeapYearFunction;
649
650impl SqlFunction for IsLeapYearFunction {
651 fn signature(&self) -> FunctionSignature {
652 FunctionSignature {
653 name: "ISLEAPYEAR",
654 category: FunctionCategory::Date,
655 arg_count: ArgCount::Fixed(1),
656 description: "Returns true if the year is a leap year",
657 returns: "BOOLEAN",
658 examples: vec![
659 "SELECT ISLEAPYEAR('2024-01-01')", "SELECT ISLEAPYEAR(2024)", "SELECT ISLEAPYEAR(2023)", ],
663 }
664 }
665
666 fn evaluate(&self, args: &[DataValue]) -> Result<DataValue> {
667 if args.len() != 1 {
668 return Err(anyhow!("ISLEAPYEAR expects exactly 1 argument"));
669 }
670
671 let year = match &args[0] {
672 DataValue::Integer(y) => *y as i32,
673 DataValue::Float(f) => *f as i32,
674 DataValue::String(s) | DataValue::DateTime(s) => {
675 if let Ok(y) = s.parse::<i32>() {
677 y
678 } else {
679 let dt = parse_datetime(s.as_str())?;
681 dt.year()
682 }
683 }
684 DataValue::InternedString(s) => {
685 if let Ok(y) = s.parse::<i32>() {
687 y
688 } else {
689 let dt = parse_datetime(s.as_str())?;
691 dt.year()
692 }
693 }
694 DataValue::Null => return Ok(DataValue::Null),
695 _ => return Err(anyhow!("ISLEAPYEAR expects a year number or date")),
696 };
697
698 let is_leap = (year % 4 == 0 && year % 100 != 0) || (year % 400 == 0);
700
701 Ok(DataValue::Boolean(is_leap))
702 }
703}
704
705pub struct WeekOfYearFunction;
707
708impl SqlFunction for WeekOfYearFunction {
709 fn signature(&self) -> FunctionSignature {
710 FunctionSignature {
711 name: "WEEKOFYEAR",
712 category: FunctionCategory::Date,
713 arg_count: ArgCount::Fixed(1),
714 description: "Returns the ISO week number of the year (1-53)",
715 returns: "INTEGER",
716 examples: vec![
717 "SELECT WEEKOFYEAR('2024-01-01')", "SELECT WEEKOFYEAR(NOW())",
719 ],
720 }
721 }
722
723 fn evaluate(&self, args: &[DataValue]) -> Result<DataValue> {
724 if args.len() != 1 {
725 return Err(anyhow!("WEEKOFYEAR expects exactly 1 argument"));
726 }
727
728 let date_str = match &args[0] {
729 DataValue::String(s) | DataValue::DateTime(s) => s.as_str(),
730 DataValue::InternedString(s) => s.as_str(),
731 DataValue::Null => return Ok(DataValue::Null),
732 _ => return Err(anyhow!("WEEKOFYEAR expects a date/datetime string")),
733 };
734
735 let dt = parse_datetime(date_str)?;
736 let week = dt.iso_week().week();
737
738 Ok(DataValue::Integer(week as i64))
739 }
740}
741
742pub struct QuarterFunction;
744
745impl SqlFunction for QuarterFunction {
746 fn signature(&self) -> FunctionSignature {
747 FunctionSignature {
748 name: "QUARTER",
749 category: FunctionCategory::Date,
750 arg_count: ArgCount::Fixed(1),
751 description: "Returns the quarter of the year (1-4)",
752 returns: "INTEGER",
753 examples: vec![
754 "SELECT QUARTER('2024-01-15')", "SELECT QUARTER('2024-07-01')", ],
757 }
758 }
759
760 fn evaluate(&self, args: &[DataValue]) -> Result<DataValue> {
761 if args.len() != 1 {
762 return Err(anyhow!("QUARTER expects exactly 1 argument"));
763 }
764
765 let date_str = match &args[0] {
766 DataValue::String(s) | DataValue::DateTime(s) => s.as_str(),
767 DataValue::InternedString(s) => s.as_str(),
768 DataValue::Null => return Ok(DataValue::Null),
769 _ => return Err(anyhow!("QUARTER expects a date/datetime string")),
770 };
771
772 let dt = parse_datetime(date_str)?;
773 let month = dt.month();
774 let quarter = (month - 1) / 3 + 1;
775
776 Ok(DataValue::Integer(quarter as i64))
777 }
778}
779
780pub struct YearFunction;
782
783impl SqlFunction for YearFunction {
784 fn signature(&self) -> FunctionSignature {
785 FunctionSignature {
786 name: "YEAR",
787 category: FunctionCategory::Date,
788 arg_count: ArgCount::Fixed(1),
789 description: "Returns the year from a date",
790 returns: "INTEGER",
791 examples: vec![
792 "SELECT YEAR('2024-03-15')", "SELECT YEAR(NOW())", ],
795 }
796 }
797
798 fn evaluate(&self, args: &[DataValue]) -> Result<DataValue> {
799 if args.len() != 1 {
800 return Err(anyhow!("YEAR expects exactly 1 argument"));
801 }
802
803 let date_str = match &args[0] {
804 DataValue::String(s) | DataValue::DateTime(s) => s.as_str(),
805 DataValue::InternedString(s) => s.as_str(),
806 DataValue::Null => return Ok(DataValue::Null),
807 _ => return Err(anyhow!("YEAR expects a date/datetime string")),
808 };
809
810 let dt = parse_datetime(date_str)?;
811 Ok(DataValue::Float(dt.year() as f64))
812 }
813}
814
815pub struct MonthFunction;
817
818impl SqlFunction for MonthFunction {
819 fn signature(&self) -> FunctionSignature {
820 FunctionSignature {
821 name: "MONTH",
822 category: FunctionCategory::Date,
823 arg_count: ArgCount::Fixed(1),
824 description: "Returns the month from a date (1-12)",
825 returns: "INTEGER",
826 examples: vec![
827 "SELECT MONTH('2024-03-15')", "SELECT MONTH(NOW())", ],
830 }
831 }
832
833 fn evaluate(&self, args: &[DataValue]) -> Result<DataValue> {
834 if args.len() != 1 {
835 return Err(anyhow!("MONTH expects exactly 1 argument"));
836 }
837
838 let date_str = match &args[0] {
839 DataValue::String(s) | DataValue::DateTime(s) => s.as_str(),
840 DataValue::InternedString(s) => s.as_str(),
841 DataValue::Null => return Ok(DataValue::Null),
842 _ => return Err(anyhow!("MONTH expects a date/datetime string")),
843 };
844
845 let dt = parse_datetime(date_str)?;
846 Ok(DataValue::Float(dt.month() as f64))
847 }
848}
849
850pub struct DayFunction;
852
853impl SqlFunction for DayFunction {
854 fn signature(&self) -> FunctionSignature {
855 FunctionSignature {
856 name: "DAY",
857 category: FunctionCategory::Date,
858 arg_count: ArgCount::Fixed(1),
859 description: "Returns the day of month from a date (1-31)",
860 returns: "INTEGER",
861 examples: vec![
862 "SELECT DAY('2024-03-15')", "SELECT DAY(NOW())", ],
865 }
866 }
867
868 fn evaluate(&self, args: &[DataValue]) -> Result<DataValue> {
869 if args.len() != 1 {
870 return Err(anyhow!("DAY expects exactly 1 argument"));
871 }
872
873 let date_str = match &args[0] {
874 DataValue::String(s) | DataValue::DateTime(s) => s.as_str(),
875 DataValue::InternedString(s) => s.as_str(),
876 DataValue::Null => return Ok(DataValue::Null),
877 _ => return Err(anyhow!("DAY expects a date/datetime string")),
878 };
879
880 let dt = parse_datetime(date_str)?;
881 Ok(DataValue::Float(dt.day() as f64))
882 }
883}
884
885pub struct MonthNameFunction;
887
888impl SqlFunction for MonthNameFunction {
889 fn signature(&self) -> FunctionSignature {
890 FunctionSignature {
891 name: "MONTHNAME",
892 category: FunctionCategory::Date,
893 arg_count: ArgCount::Range(1, 2),
894 description: "Returns month name. Optional second arg: 'full' (default) or 'short'",
895 returns: "STRING",
896 examples: vec![
897 "SELECT MONTHNAME('2024-01-15')", "SELECT MONTHNAME('2024-01-15', 'short')", ],
900 }
901 }
902
903 fn evaluate(&self, args: &[DataValue]) -> Result<DataValue> {
904 if args.is_empty() || args.len() > 2 {
905 return Err(anyhow!("MONTHNAME expects 1 or 2 arguments"));
906 }
907
908 let date_str = match &args[0] {
909 DataValue::String(s) | DataValue::DateTime(s) => s.as_str(),
910 DataValue::InternedString(s) => s.as_str(),
911 DataValue::Null => return Ok(DataValue::Null),
912 _ => return Err(anyhow!("MONTHNAME expects a date/datetime string")),
913 };
914
915 let format = if args.len() == 2 {
916 match &args[1] {
917 DataValue::String(s) => s.as_str(),
918 DataValue::InternedString(s) => s.as_str(),
919 DataValue::Null => "full",
920 _ => return Err(anyhow!("MONTHNAME format must be 'full' or 'short'")),
921 }
922 } else {
923 "full"
924 };
925
926 let dt = parse_datetime(date_str)?;
927
928 let month_name = match format {
929 "short" => dt.format("%b").to_string(), "full" | _ => dt.format("%B").to_string(), };
932
933 Ok(DataValue::String(month_name))
934 }
935}
936
937pub struct ParseDateTimeFunction;
939
940impl SqlFunction for ParseDateTimeFunction {
941 fn signature(&self) -> FunctionSignature {
942 FunctionSignature {
943 name: "PARSE_DATETIME",
944 category: FunctionCategory::Date,
945 arg_count: ArgCount::Fixed(2),
946 description: "Parse datetime string with custom format (uses chrono strftime format)",
947 returns: "DATETIME",
948 examples: vec![
949 "SELECT PARSE_DATETIME('15/01/2024', '%d/%m/%Y')",
950 "SELECT PARSE_DATETIME('Jan 15, 2024 14:30', '%b %d, %Y %H:%M')",
951 "SELECT PARSE_DATETIME('2024-01-15T14:30:00', '%Y-%m-%dT%H:%M:%S')",
952 "SELECT PARSE_DATETIME(date_string, '%Y%m%d-%H:%M:%S%.3f') FROM data -- FIX format",
953 ],
954 }
955 }
956
957 fn evaluate(&self, args: &[DataValue]) -> Result<DataValue> {
958 self.validate_args(args)?;
959
960 let date_str = match &args[0] {
961 DataValue::String(s) | DataValue::DateTime(s) => s.as_str(),
962 DataValue::InternedString(s) => s.as_str(),
963 DataValue::Null => return Ok(DataValue::Null),
964 _ => return Err(anyhow!("PARSE_DATETIME expects a string as first argument")),
965 };
966
967 let format_str = match &args[1] {
968 DataValue::String(s) => s.as_str(),
969 DataValue::InternedString(s) => s.as_str(),
970 DataValue::Null => return Ok(DataValue::Null),
971 _ => {
972 return Err(anyhow!(
973 "PARSE_DATETIME expects a format string as second argument"
974 ))
975 }
976 };
977
978 if let Ok(dt) = NaiveDateTime::parse_from_str(date_str, format_str) {
980 return Ok(DataValue::DateTime(
981 Utc.from_utc_datetime(&dt)
982 .format("%Y-%m-%d %H:%M:%S%.3f")
983 .to_string(),
984 ));
985 }
986
987 if let Ok(d) = NaiveDate::parse_from_str(date_str, format_str) {
989 return Ok(DataValue::DateTime(
990 Utc.from_utc_datetime(&d.and_hms_opt(0, 0, 0).unwrap())
991 .format("%Y-%m-%d %H:%M:%S%.3f")
992 .to_string(),
993 ));
994 }
995
996 Err(anyhow!(
997 "Failed to parse '{}' with format '{}'. See https://docs.rs/chrono/latest/chrono/format/strftime/index.html",
998 date_str,
999 format_str
1000 ))
1001 }
1002}
1003
1004pub struct ParseDateTimeUtcFunction;
1006
1007impl SqlFunction for ParseDateTimeUtcFunction {
1008 fn signature(&self) -> FunctionSignature {
1009 FunctionSignature {
1010 name: "PARSE_DATETIME_UTC",
1011 category: FunctionCategory::Date,
1012 arg_count: ArgCount::Range(1, 2),
1013 description: "Parse datetime as UTC. With 1 arg: auto-detect format. With 2 args: use custom format",
1014 returns: "DATETIME (UTC)",
1015 examples: vec![
1016 "SELECT PARSE_DATETIME_UTC('2024-01-15 14:30:00')",
1017 "SELECT PARSE_DATETIME_UTC('20250925-14:52:15.567') -- FIX format auto-detected",
1018 "SELECT PARSE_DATETIME_UTC('15/01/2024 14:30', '%d/%m/%Y %H:%M')",
1019 ],
1020 }
1021 }
1022
1023 fn evaluate(&self, args: &[DataValue]) -> Result<DataValue> {
1024 if args.is_empty() || args.len() > 2 {
1025 return Err(anyhow!("PARSE_DATETIME_UTC expects 1 or 2 arguments"));
1026 }
1027
1028 let date_str = match &args[0] {
1029 DataValue::String(s) | DataValue::DateTime(s) => s.as_str(),
1030 DataValue::InternedString(s) => s.as_str(),
1031 DataValue::Null => return Ok(DataValue::Null),
1032 _ => {
1033 return Err(anyhow!(
1034 "PARSE_DATETIME_UTC expects a string as first argument"
1035 ))
1036 }
1037 };
1038
1039 if args.len() == 2 {
1041 let format_str = match &args[1] {
1042 DataValue::String(s) => s.as_str(),
1043 DataValue::InternedString(s) => s.as_str(),
1044 DataValue::Null => return Ok(DataValue::Null),
1045 _ => {
1046 return Err(anyhow!(
1047 "PARSE_DATETIME_UTC expects a format string as second argument"
1048 ))
1049 }
1050 };
1051
1052 if let Ok(dt) = NaiveDateTime::parse_from_str(date_str, format_str) {
1054 return Ok(DataValue::DateTime(
1055 Utc.from_utc_datetime(&dt)
1056 .format("%Y-%m-%d %H:%M:%S%.3f")
1057 .to_string(),
1058 ));
1059 }
1060
1061 if let Ok(d) = NaiveDate::parse_from_str(date_str, format_str) {
1063 return Ok(DataValue::DateTime(
1064 Utc.from_utc_datetime(&d.and_hms_opt(0, 0, 0).unwrap())
1065 .format("%Y-%m-%d %H:%M:%S%.3f")
1066 .to_string(),
1067 ));
1068 }
1069
1070 return Err(anyhow!(
1071 "Failed to parse '{}' with format '{}'",
1072 date_str,
1073 format_str
1074 ));
1075 }
1076
1077 let dt = parse_datetime(date_str)?;
1079 Ok(DataValue::DateTime(
1080 dt.format("%Y-%m-%d %H:%M:%S%.3f").to_string(),
1081 ))
1082 }
1083}
1084
1085pub struct DateTimeConstructor;
1087
1088impl SqlFunction for DateTimeConstructor {
1089 fn signature(&self) -> FunctionSignature {
1090 FunctionSignature {
1091 name: "DATETIME",
1092 category: FunctionCategory::Date,
1093 arg_count: ArgCount::Range(3, 7),
1094 description: "Create datetime from components: (year, month, day, [hour], [minute], [second], [is_utc])",
1095 returns: "DATETIME",
1096 examples: vec![
1097 "SELECT DATETIME(2024, 1, 15)",
1098 "SELECT DATETIME(2024, 1, 15, 14, 30, 0)",
1099 "SELECT DATETIME(2024, 12, 31, 23, 59, 59)",
1100 "-- Note: All times are interpreted as UTC",
1101 ],
1102 }
1103 }
1104
1105 fn evaluate(&self, args: &[DataValue]) -> Result<DataValue> {
1106 if args.len() < 3 || args.len() > 7 {
1107 return Err(anyhow!("DATETIME expects 3-7 arguments: year, month, day, [hour], [minute], [second], [is_utc]"));
1108 }
1109
1110 let year = match &args[0] {
1112 DataValue::Integer(i) => *i as i32,
1113 DataValue::Float(f) => *f as i32,
1114 DataValue::Null => return Ok(DataValue::Null),
1115 _ => return Err(anyhow!("DATETIME year must be numeric")),
1116 };
1117
1118 let month = match &args[1] {
1119 DataValue::Integer(i) => *i as u32,
1120 DataValue::Float(f) => *f as u32,
1121 DataValue::Null => return Ok(DataValue::Null),
1122 _ => return Err(anyhow!("DATETIME month must be numeric")),
1123 };
1124
1125 let day = match &args[2] {
1126 DataValue::Integer(i) => *i as u32,
1127 DataValue::Float(f) => *f as u32,
1128 DataValue::Null => return Ok(DataValue::Null),
1129 _ => return Err(anyhow!("DATETIME day must be numeric")),
1130 };
1131
1132 let hour = if args.len() > 3 {
1133 match &args[3] {
1134 DataValue::Integer(i) => *i as u32,
1135 DataValue::Float(f) => *f as u32,
1136 DataValue::Null => return Ok(DataValue::Null),
1137 _ => return Err(anyhow!("DATETIME hour must be numeric")),
1138 }
1139 } else {
1140 0
1141 };
1142
1143 let minute = if args.len() > 4 {
1144 match &args[4] {
1145 DataValue::Integer(i) => *i as u32,
1146 DataValue::Float(f) => *f as u32,
1147 DataValue::Null => return Ok(DataValue::Null),
1148 _ => return Err(anyhow!("DATETIME minute must be numeric")),
1149 }
1150 } else {
1151 0
1152 };
1153
1154 let second = if args.len() > 5 {
1155 match &args[5] {
1156 DataValue::Integer(i) => *i as u32,
1157 DataValue::Float(f) => *f as u32,
1158 DataValue::Null => return Ok(DataValue::Null),
1159 _ => return Err(anyhow!("DATETIME second must be numeric")),
1160 }
1161 } else {
1162 0
1163 };
1164
1165 let _is_utc = if args.len() > 6 {
1167 match &args[6] {
1168 DataValue::Boolean(b) => *b,
1169 DataValue::Integer(i) => *i != 0,
1170 DataValue::Null => true,
1171 _ => return Err(anyhow!("DATETIME is_utc must be boolean")),
1172 }
1173 } else {
1174 true
1175 };
1176
1177 let date = NaiveDate::from_ymd_opt(year, month, day)
1179 .ok_or_else(|| anyhow!("Invalid date: {}-{}-{}", year, month, day))?;
1180
1181 let dt = date
1183 .and_hms_opt(hour, minute, second)
1184 .ok_or_else(|| anyhow!("Invalid time: {}:{}:{}", hour, minute, second))?;
1185
1186 let utc_dt = Utc.from_utc_datetime(&dt);
1188
1189 Ok(DataValue::DateTime(
1190 utc_dt.format("%Y-%m-%d %H:%M:%S%.3f").to_string(),
1191 ))
1192 }
1193}
1194
1195pub fn register_date_time_functions(registry: &mut super::FunctionRegistry) {
1197 registry.register(Box::new(NowFunction));
1198 registry.register(Box::new(TodayFunction));
1199 registry.register(Box::new(DateDiffFunction));
1200 registry.register(Box::new(DateAddFunction));
1201 registry.register(Box::new(UnixTimestamp));
1202 registry.register(Box::new(FromUnixTime));
1203 registry.register(Box::new(TimeBucket));
1204
1205 registry.register(Box::new(YearFunction));
1207 registry.register(Box::new(MonthFunction));
1208 registry.register(Box::new(DayFunction));
1209 registry.register(Box::new(DayOfWeekFunction));
1210 registry.register(Box::new(DayNameFunction));
1211 registry.register(Box::new(MonthNameFunction));
1212
1213 registry.register(Box::new(IsLeapYearFunction));
1215 registry.register(Box::new(WeekOfYearFunction));
1216 registry.register(Box::new(QuarterFunction));
1217
1218 registry.register(Box::new(ParseDateTimeFunction));
1220 registry.register(Box::new(ParseDateTimeUtcFunction));
1221 registry.register(Box::new(DateTimeConstructor));
1222}