Skip to main content

datafusion_functions_table/
generate_series.rs

1// Licensed to the Apache Software Foundation (ASF) under one
2// or more contributor license agreements.  See the NOTICE file
3// distributed with this work for additional information
4// regarding copyright ownership.  The ASF licenses this file
5// to you under the Apache License, Version 2.0 (the
6// "License"); you may not use this file except in compliance
7// with the License.  You may obtain a copy of the License at
8//
9//   http://www.apache.org/licenses/LICENSE-2.0
10//
11// Unless required by applicable law or agreed to in writing,
12// software distributed under the License is distributed on an
13// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
14// KIND, either express or implied.  See the License for the
15// specific language governing permissions and limitations
16// under the License.
17
18use arrow::array::timezone::Tz;
19use arrow::array::types::TimestampNanosecondType;
20use arrow::array::{ArrayRef, Int64Array, TimestampNanosecondArray};
21use arrow::compute::SortOptions;
22use arrow::datatypes::{
23    DataType, Field, IntervalMonthDayNano, Schema, SchemaRef, TimeUnit,
24};
25use arrow::record_batch::RecordBatch;
26use async_trait::async_trait;
27use datafusion_catalog::TableFunctionImpl;
28use datafusion_catalog::TableProvider;
29use datafusion_catalog::{Session, TableFunctionArgs};
30use datafusion_common::{Result, ScalarValue, plan_datafusion_err, plan_err};
31use datafusion_expr::{Expr, TableType};
32use datafusion_physical_expr::PhysicalSortExpr;
33use datafusion_physical_expr::expressions::Column;
34use datafusion_physical_plan::ExecutionPlan;
35use datafusion_physical_plan::memory::{LazyBatchGenerator, LazyMemoryExec};
36use parking_lot::RwLock;
37use std::any::Any;
38use std::fmt;
39use std::str::FromStr;
40use std::sync::Arc;
41
42/// Empty generator that produces no rows - used when series arguments contain null values
43#[derive(Debug, Clone)]
44pub struct Empty {
45    name: &'static str,
46}
47
48impl Empty {
49    pub fn name(&self) -> &'static str {
50        self.name
51    }
52}
53
54impl LazyBatchGenerator for Empty {
55    fn as_any(&self) -> &dyn Any {
56        self
57    }
58
59    fn generate_next_batch(&mut self) -> Result<Option<RecordBatch>> {
60        Ok(None)
61    }
62
63    fn reset_state(&self) -> Arc<RwLock<dyn LazyBatchGenerator>> {
64        Arc::new(RwLock::new(Empty { name: self.name }))
65    }
66}
67
68impl fmt::Display for Empty {
69    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
70        write!(f, "{}: empty", self.name)
71    }
72}
73
74/// Trait for values that can be generated in a series
75pub trait SeriesValue: fmt::Debug + Clone + Send + Sync + 'static {
76    type StepType: fmt::Debug + Clone + Send + Sync;
77    type ValueType: fmt::Debug + Clone + Send + Sync;
78
79    /// Check if we've reached the end of the series
80    fn should_stop(&self, end: Self, step: &Self::StepType, include_end: bool) -> bool;
81
82    /// Advance to the next value in the series.
83    fn advance(&mut self, step: &Self::StepType) -> Result<()>;
84
85    /// Advance to the next value, adjusting the end of the series if needed.
86    ///
87    /// The default implementation preserves the behavior of [`Self::advance`].
88    /// Implementations can override this method when they need to handle an
89    /// overflow by terminating the series after the current value.
90    fn advance_with_end(&mut self, _end: &mut Self, step: &Self::StepType) -> Result<()> {
91        self.advance(step)
92    }
93
94    /// Create an Arrow array from a vector of values
95    fn create_array(&self, values: Vec<Self::ValueType>) -> Result<ArrayRef>;
96
97    /// Convert self to ValueType for array creation
98    fn to_value_type(&self) -> Self::ValueType;
99
100    /// Display the value for debugging
101    fn display_value(&self) -> String;
102}
103
104impl SeriesValue for i64 {
105    type StepType = i64;
106    type ValueType = i64;
107
108    fn should_stop(&self, end: Self, step: &Self::StepType, include_end: bool) -> bool {
109        reach_end_int64(*self, end, *step, include_end)
110    }
111
112    fn advance(&mut self, step: &Self::StepType) -> Result<()> {
113        *self += step;
114        Ok(())
115    }
116
117    fn advance_with_end(&mut self, end: &mut Self, step: &Self::StepType) -> Result<()> {
118        if let Some(next) = self.checked_add(*step) {
119            *self = next;
120        } else {
121            // Advancing would overflow: clamp `end` so the series stops after
122            // the current (last reachable) value instead of panicking or
123            // wrapping around.
124            *end = if *step > 0 {
125                self.saturating_sub(1)
126            } else {
127                self.saturating_add(1)
128            };
129        }
130        Ok(())
131    }
132
133    fn create_array(&self, values: Vec<Self::ValueType>) -> Result<ArrayRef> {
134        Ok(Arc::new(Int64Array::from(values)))
135    }
136
137    fn to_value_type(&self) -> Self::ValueType {
138        *self
139    }
140
141    fn display_value(&self) -> String {
142        self.to_string()
143    }
144}
145
146#[derive(Debug, Clone)]
147pub struct TimestampValue {
148    value: i64,
149    parsed_tz: Option<Tz>,
150    tz_str: Option<Arc<str>>,
151}
152
153impl TimestampValue {
154    pub fn value(&self) -> i64 {
155        self.value
156    }
157
158    pub fn tz_str(&self) -> Option<&Arc<str>> {
159        self.tz_str.as_ref()
160    }
161}
162
163impl SeriesValue for TimestampValue {
164    type StepType = IntervalMonthDayNano;
165    type ValueType = i64;
166
167    fn should_stop(&self, end: Self, step: &Self::StepType, include_end: bool) -> bool {
168        let step_negative = step.months < 0 || step.days < 0 || step.nanoseconds < 0;
169
170        if include_end {
171            if step_negative {
172                self.value < end.value
173            } else {
174                self.value > end.value
175            }
176        } else if step_negative {
177            self.value <= end.value
178        } else {
179            self.value >= end.value
180        }
181    }
182
183    fn advance(&mut self, step: &Self::StepType) -> Result<()> {
184        let tz = self
185            .parsed_tz
186            .unwrap_or_else(|| Tz::from_str("+00:00").unwrap());
187        let Some(next_ts) =
188            TimestampNanosecondType::add_month_day_nano(self.value, *step, tz)
189        else {
190            return plan_err!(
191                "Failed to add interval {:?} to timestamp {}",
192                step,
193                self.value
194            );
195        };
196        self.value = next_ts;
197        Ok(())
198    }
199
200    fn advance_with_end(&mut self, end: &mut Self, step: &Self::StepType) -> Result<()> {
201        let tz = self
202            .parsed_tz
203            .unwrap_or_else(|| Tz::from_str("+00:00").unwrap());
204        if let Some(next_ts) =
205            TimestampNanosecondType::add_month_day_nano(self.value, *step, tz)
206        {
207            self.value = next_ts;
208        } else {
209            // Advancing would exceed the timestamp range. Clamp `end` so the
210            // series terminates after the current (last reachable) value.
211            let step_negative = step.months < 0 || step.days < 0 || step.nanoseconds < 0;
212            end.value = if step_negative {
213                self.value.saturating_add(1)
214            } else {
215                self.value.saturating_sub(1)
216            };
217        }
218        Ok(())
219    }
220
221    fn create_array(&self, values: Vec<Self::ValueType>) -> Result<ArrayRef> {
222        let array = TimestampNanosecondArray::from(values);
223
224        // Use timezone from self (now we have access to tz through &self)
225        let array = match self.tz_str.as_ref() {
226            Some(tz_str) => array.with_timezone(Arc::clone(tz_str)),
227            None => array,
228        };
229
230        Ok(Arc::new(array))
231    }
232
233    fn to_value_type(&self) -> Self::ValueType {
234        self.value
235    }
236
237    fn display_value(&self) -> String {
238        self.value.to_string()
239    }
240}
241
242/// Indicates the arguments used for generating a series.
243#[derive(Debug, Clone)]
244pub enum GenSeriesArgs {
245    /// ContainsNull signifies that at least one argument(start, end, step) was null, thus no series will be generated.
246    ContainsNull { name: &'static str },
247    /// Int64Args holds the start, end, and step values for generating integer series when all arguments are not null.
248    Int64Args {
249        start: i64,
250        end: i64,
251        step: i64,
252        /// Indicates whether the end value should be included in the series.
253        include_end: bool,
254        name: &'static str,
255    },
256    /// TimestampArgs holds the start, end, and step values for generating timestamp series when all arguments are not null.
257    TimestampArgs {
258        start: i64,
259        end: i64,
260        step: IntervalMonthDayNano,
261        tz: Option<Arc<str>>,
262        /// Indicates whether the end value should be included in the series.
263        include_end: bool,
264        name: &'static str,
265    },
266    /// DateArgs holds the start, end, and step values for generating date series when all arguments are not null.
267    /// Internally, dates are converted to timestamps and use the timestamp logic.
268    DateArgs {
269        start: i64,
270        end: i64,
271        step: IntervalMonthDayNano,
272        /// Indicates whether the end value should be included in the series.
273        include_end: bool,
274        name: &'static str,
275    },
276}
277
278/// Table that generates a series of integers/timestamps from `start`(inclusive) to `end`, incrementing by step
279#[derive(Debug, Clone)]
280pub struct GenerateSeriesTable {
281    schema: SchemaRef,
282    args: GenSeriesArgs,
283}
284
285impl GenerateSeriesTable {
286    pub fn new(schema: SchemaRef, args: GenSeriesArgs) -> Self {
287        Self { schema, args }
288    }
289
290    pub fn as_generator(
291        &self,
292        batch_size: usize,
293    ) -> Result<Arc<RwLock<dyn LazyBatchGenerator>>> {
294        let generator: Arc<RwLock<dyn LazyBatchGenerator>> = match &self.args {
295            GenSeriesArgs::ContainsNull { name } => Arc::new(RwLock::new(Empty { name })),
296            GenSeriesArgs::Int64Args {
297                start,
298                end,
299                step,
300                include_end,
301                name,
302            } => Arc::new(RwLock::new(GenericSeriesState {
303                schema: self.schema(),
304                start: *start,
305                end: *end,
306                step: *step,
307                current: *start,
308                finished: false,
309                batch_size,
310                include_end: *include_end,
311                name,
312            })),
313            GenSeriesArgs::TimestampArgs {
314                start,
315                end,
316                step,
317                tz,
318                include_end,
319                name,
320            } => {
321                let parsed_tz = tz
322                    .as_ref()
323                    .map(|s| Tz::from_str(s.as_ref()))
324                    .transpose()
325                    .map_err(|e| {
326                        datafusion_common::internal_datafusion_err!(
327                            "Failed to parse timezone: {e}"
328                        )
329                    })?
330                    .unwrap_or_else(|| Tz::from_str("+00:00").unwrap());
331                Arc::new(RwLock::new(GenericSeriesState {
332                    schema: self.schema(),
333                    start: TimestampValue {
334                        value: *start,
335                        parsed_tz: Some(parsed_tz),
336                        tz_str: tz.clone(),
337                    },
338                    end: TimestampValue {
339                        value: *end,
340                        parsed_tz: Some(parsed_tz),
341                        tz_str: tz.clone(),
342                    },
343                    step: *step,
344                    current: TimestampValue {
345                        value: *start,
346                        parsed_tz: Some(parsed_tz),
347                        tz_str: tz.clone(),
348                    },
349                    finished: false,
350                    batch_size,
351                    include_end: *include_end,
352                    name,
353                }))
354            }
355            GenSeriesArgs::DateArgs {
356                start,
357                end,
358                step,
359                include_end,
360                name,
361            } => Arc::new(RwLock::new(GenericSeriesState {
362                schema: self.schema(),
363                start: TimestampValue {
364                    value: *start,
365                    parsed_tz: None,
366                    tz_str: None,
367                },
368                end: TimestampValue {
369                    value: *end,
370                    parsed_tz: None,
371                    tz_str: None,
372                },
373                step: *step,
374                current: TimestampValue {
375                    value: *start,
376                    parsed_tz: None,
377                    tz_str: None,
378                },
379                finished: false,
380                batch_size,
381                include_end: *include_end,
382                name,
383            })),
384        };
385
386        Ok(generator)
387    }
388
389    /// Detects output sort order to potentially remove `SortExec`.
390    /// Only the `Int64` argument type is currently supported.
391    fn output_ordering(&self, schema: &Schema) -> Option<PhysicalSortExpr> {
392        let step = match &self.args {
393            GenSeriesArgs::Int64Args { step, .. } => *step,
394            _ => return None,
395        };
396
397        if schema.fields().is_empty() {
398            return None;
399        }
400
401        let descending = step < 0;
402        Some(PhysicalSortExpr::new(
403            Arc::new(Column::new(schema.field(0).name(), 0)),
404            SortOptions {
405                descending,
406                // this table function won't output nulls, so either is fine
407                nulls_first: false,
408            },
409        ))
410    }
411}
412
413#[derive(Debug, Clone)]
414pub struct GenericSeriesState<T: SeriesValue> {
415    schema: SchemaRef,
416    start: T,
417    end: T,
418    step: T::StepType,
419    batch_size: usize,
420    current: T,
421    finished: bool,
422    include_end: bool,
423    name: &'static str,
424}
425
426impl<T: SeriesValue> GenericSeriesState<T> {
427    pub fn name(&self) -> &'static str {
428        self.name
429    }
430
431    pub fn batch_size(&self) -> usize {
432        self.batch_size
433    }
434
435    pub fn include_end(&self) -> bool {
436        self.include_end
437    }
438
439    pub fn start(&self) -> &T {
440        &self.start
441    }
442
443    pub fn end(&self) -> &T {
444        &self.end
445    }
446
447    pub fn step(&self) -> &T::StepType {
448        &self.step
449    }
450
451    pub fn current(&self) -> &T {
452        &self.current
453    }
454}
455
456impl<T: SeriesValue> LazyBatchGenerator for GenericSeriesState<T> {
457    fn as_any(&self) -> &dyn Any {
458        self
459    }
460
461    fn generate_next_batch(&mut self) -> Result<Option<RecordBatch>> {
462        if self.finished {
463            return Ok(None);
464        }
465
466        let mut buf = Vec::with_capacity(self.batch_size);
467
468        while buf.len() < self.batch_size
469            && !self
470                .current
471                .should_stop(self.end.clone(), &self.step, self.include_end)
472        {
473            buf.push(self.current.to_value_type());
474            if self
475                .current
476                .should_stop(self.end.clone(), &self.step, false)
477            {
478                self.finished = true;
479                break;
480            }
481
482            let original_end = self.end.clone();
483            self.current.advance_with_end(&mut self.end, &self.step)?;
484            if self
485                .current
486                .should_stop(self.end.clone(), &self.step, self.include_end)
487            {
488                self.end = original_end;
489                self.finished = true;
490                break;
491            }
492        }
493
494        if buf.is_empty() {
495            return Ok(None);
496        }
497
498        let array = self.current.create_array(buf)?;
499        let batch = RecordBatch::try_new(Arc::clone(&self.schema), vec![array])?;
500        Ok(Some(batch))
501    }
502
503    fn reset_state(&self) -> Arc<RwLock<dyn LazyBatchGenerator>> {
504        let mut new = self.clone();
505        new.current = new.start.clone();
506        new.finished = false;
507        Arc::new(RwLock::new(new))
508    }
509}
510
511impl<T: SeriesValue> fmt::Display for GenericSeriesState<T> {
512    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
513        write!(
514            f,
515            "{}: start={}, end={}, batch_size={}",
516            self.name,
517            self.start.display_value(),
518            self.end.display_value(),
519            self.batch_size
520        )
521    }
522}
523
524fn reach_end_int64(val: i64, end: i64, step: i64, include_end: bool) -> bool {
525    if step > 0 {
526        if include_end { val > end } else { val >= end }
527    } else if include_end {
528        val < end
529    } else {
530        val <= end
531    }
532}
533
534fn validate_interval_step(step: IntervalMonthDayNano) -> Result<()> {
535    if step.months == 0 && step.days == 0 && step.nanoseconds == 0 {
536        return plan_err!("Step interval cannot be zero");
537    }
538
539    Ok(())
540}
541
542#[async_trait]
543impl TableProvider for GenerateSeriesTable {
544    fn schema(&self) -> SchemaRef {
545        Arc::clone(&self.schema)
546    }
547
548    fn table_type(&self) -> TableType {
549        TableType::Base
550    }
551
552    async fn scan(
553        &self,
554        state: &dyn Session,
555        projection: Option<&Vec<usize>>,
556        _filters: &[Expr],
557        _limit: Option<usize>,
558    ) -> Result<Arc<dyn ExecutionPlan>> {
559        let batch_size = state.config_options().execution.batch_size.get();
560        let generator = self.as_generator(batch_size)?;
561        let mut exec = LazyMemoryExec::try_new(self.schema(), vec![generator])?
562            .with_projection(projection.cloned());
563
564        if let Some(ordering) = self.output_ordering(exec.schema().as_ref()) {
565            exec.add_ordering([ordering]);
566        }
567
568        Ok(Arc::new(exec))
569    }
570}
571
572#[derive(Debug)]
573struct GenerateSeriesFuncImpl {
574    name: &'static str,
575    include_end: bool,
576}
577
578impl TableFunctionImpl for GenerateSeriesFuncImpl {
579    fn call_with_args(&self, args: TableFunctionArgs) -> Result<Arc<dyn TableProvider>> {
580        let exprs = args.exprs();
581        if exprs.is_empty() || exprs.len() > 3 {
582            return plan_err!("{} function requires 1 to 3 arguments", self.name);
583        }
584
585        // Determine the data type from the first argument
586        match &exprs[0] {
587            Expr::Literal(
588                // Default to int64 for null
589                ScalarValue::Null | ScalarValue::Int64(_),
590                _,
591            ) => self.call_int64(exprs),
592            Expr::Literal(s, _) if matches!(s.data_type(), DataType::Timestamp(_, _)) => {
593                self.call_timestamp(exprs)
594            }
595            Expr::Literal(s, _) if matches!(s.data_type(), DataType::Date32) => {
596                self.call_date(exprs)
597            }
598            Expr::Literal(scalar, _) => {
599                plan_err!(
600                    "Argument #1 must be an INTEGER, TIMESTAMP, DATE or NULL, got {:?}",
601                    scalar.data_type()
602                )
603            }
604            _ => plan_err!("Arguments must be literals"),
605        }
606    }
607}
608
609impl GenerateSeriesFuncImpl {
610    fn call_int64(&self, exprs: &[Expr]) -> Result<Arc<dyn TableProvider>> {
611        let mut normalize_args = Vec::new();
612        for (expr_index, expr) in exprs.iter().enumerate() {
613            match expr {
614                Expr::Literal(ScalarValue::Null, _) => {}
615                Expr::Literal(ScalarValue::Int64(Some(n)), _) => normalize_args.push(*n),
616                other => {
617                    return plan_err!(
618                        "Argument #{} must be an INTEGER or NULL, got {:?}",
619                        expr_index + 1,
620                        other
621                    );
622                }
623            };
624        }
625
626        let schema = Arc::new(Schema::new(vec![Field::new(
627            "value",
628            DataType::Int64,
629            false,
630        )]));
631
632        if normalize_args.len() != exprs.len() {
633            // contain null
634            return Ok(Arc::new(GenerateSeriesTable {
635                schema,
636                args: GenSeriesArgs::ContainsNull { name: self.name },
637            }));
638        }
639
640        let (start, end, step) = match &normalize_args[..] {
641            [end] => (0, *end, 1),
642            [start, end] => (*start, *end, 1),
643            [start, end, step] => (*start, *end, *step),
644            _ => {
645                return plan_err!("{} function requires 1 to 3 arguments", self.name);
646            }
647        };
648
649        if step == 0 {
650            return plan_err!("Step cannot be zero");
651        }
652
653        Ok(Arc::new(GenerateSeriesTable {
654            schema,
655            args: GenSeriesArgs::Int64Args {
656                start,
657                end,
658                step,
659                include_end: self.include_end,
660                name: self.name,
661            },
662        }))
663    }
664
665    fn call_timestamp(&self, exprs: &[Expr]) -> Result<Arc<dyn TableProvider>> {
666        if exprs.len() != 3 {
667            return plan_err!(
668                "{} function with timestamps requires exactly 3 arguments",
669                self.name
670            );
671        }
672
673        // Parse start timestamp
674        let (start_ts, tz) = match &exprs[0] {
675            Expr::Literal(ScalarValue::TimestampNanosecond(ts, tz), _) => {
676                (*ts, tz.clone())
677            }
678            other => {
679                return plan_err!(
680                    "First argument must be a timestamp or NULL, got {:?}",
681                    other
682                );
683            }
684        };
685
686        // Parse end timestamp
687        let end_ts = match &exprs[1] {
688            Expr::Literal(ScalarValue::Null, _) => None,
689            Expr::Literal(ScalarValue::TimestampNanosecond(ts, _), _) => *ts,
690            other => {
691                return plan_err!(
692                    "Second argument must be a timestamp or NULL, got {:?}",
693                    other
694                );
695            }
696        };
697
698        // Parse step interval
699        let step_interval = match &exprs[2] {
700            Expr::Literal(ScalarValue::Null, _) => None,
701            Expr::Literal(ScalarValue::IntervalMonthDayNano(interval), _) => *interval,
702            other => {
703                return plan_err!(
704                    "Third argument must be an interval or NULL, got {:?}",
705                    other
706                );
707            }
708        };
709
710        let schema = Arc::new(Schema::new(vec![Field::new(
711            "value",
712            DataType::Timestamp(TimeUnit::Nanosecond, tz.clone()),
713            false,
714        )]));
715
716        // Check if any argument is null
717        let (Some(start), Some(end), Some(step)) = (start_ts, end_ts, step_interval)
718        else {
719            return Ok(Arc::new(GenerateSeriesTable {
720                schema,
721                args: GenSeriesArgs::ContainsNull { name: self.name },
722            }));
723        };
724
725        // Validate step interval
726        validate_interval_step(step)?;
727
728        Ok(Arc::new(GenerateSeriesTable {
729            schema,
730            args: GenSeriesArgs::TimestampArgs {
731                start,
732                end,
733                step,
734                tz,
735                include_end: self.include_end,
736                name: self.name,
737            },
738        }))
739    }
740
741    fn call_date(&self, exprs: &[Expr]) -> Result<Arc<dyn TableProvider>> {
742        if exprs.len() != 3 {
743            return plan_err!(
744                "{} function with dates requires exactly 3 arguments",
745                self.name
746            );
747        }
748
749        let schema = Arc::new(Schema::new(vec![Field::new(
750            "value",
751            DataType::Timestamp(TimeUnit::Nanosecond, None),
752            false,
753        )]));
754
755        // Parse start date
756        let start_date = match &exprs[0] {
757            Expr::Literal(ScalarValue::Date32(Some(date)), _) => *date,
758            Expr::Literal(ScalarValue::Date32(None), _)
759            | Expr::Literal(ScalarValue::Null, _) => {
760                return Ok(Arc::new(GenerateSeriesTable {
761                    schema,
762                    args: GenSeriesArgs::ContainsNull { name: self.name },
763                }));
764            }
765            other => {
766                return plan_err!(
767                    "First argument must be a date or NULL, got {:?}",
768                    other
769                );
770            }
771        };
772
773        // Parse end date
774        let end_date = match &exprs[1] {
775            Expr::Literal(ScalarValue::Date32(Some(date)), _) => *date,
776            Expr::Literal(ScalarValue::Date32(None), _)
777            | Expr::Literal(ScalarValue::Null, _) => {
778                return Ok(Arc::new(GenerateSeriesTable {
779                    schema,
780                    args: GenSeriesArgs::ContainsNull { name: self.name },
781                }));
782            }
783            other => {
784                return plan_err!(
785                    "Second argument must be a date or NULL, got {:?}",
786                    other
787                );
788            }
789        };
790
791        // Parse step interval
792        let step_interval = match &exprs[2] {
793            Expr::Literal(ScalarValue::IntervalMonthDayNano(Some(interval)), _) => {
794                *interval
795            }
796            Expr::Literal(ScalarValue::IntervalMonthDayNano(None), _)
797            | Expr::Literal(ScalarValue::Null, _) => {
798                return Ok(Arc::new(GenerateSeriesTable {
799                    schema,
800                    args: GenSeriesArgs::ContainsNull { name: self.name },
801                }));
802            }
803            other => {
804                return plan_err!(
805                    "Third argument must be an interval or NULL, got {:?}",
806                    other
807                );
808            }
809        };
810
811        // Convert Date32 (days since epoch) to timestamp nanoseconds (nanoseconds since epoch)
812        // Date32 is days since 1970-01-01, so multiply by nanoseconds per day
813        const NANOS_PER_DAY: i64 = 24 * 60 * 60 * 1_000_000_000;
814
815        // Dates outside the nanosecond timestamp range (1677-09-21 to
816        // 2262-04-11) cannot be represented; return an error instead of
817        // panicking (debug) or silently wrapping (release).
818        let date_to_ts_nanos = |date: i32, arg: &str| {
819            (date as i64).checked_mul(NANOS_PER_DAY).ok_or_else(|| {
820                plan_datafusion_err!(
821                    "{arg} for {} is out of range of nanosecond timestamps",
822                    self.name
823                )
824            })
825        };
826
827        let start_ts = date_to_ts_nanos(start_date, "First argument")?;
828        let end_ts = date_to_ts_nanos(end_date, "Second argument")?;
829
830        // Validate step interval
831        validate_interval_step(step_interval)?;
832
833        Ok(Arc::new(GenerateSeriesTable {
834            schema,
835            args: GenSeriesArgs::DateArgs {
836                start: start_ts,
837                end: end_ts,
838                step: step_interval,
839                include_end: self.include_end,
840                name: self.name,
841            },
842        }))
843    }
844}
845
846#[derive(Debug)]
847pub struct GenerateSeriesFunc {}
848
849impl TableFunctionImpl for GenerateSeriesFunc {
850    fn call_with_args(&self, args: TableFunctionArgs) -> Result<Arc<dyn TableProvider>> {
851        let impl_func = GenerateSeriesFuncImpl {
852            name: "generate_series",
853            include_end: true,
854        };
855        impl_func.call_with_args(args)
856    }
857}
858
859#[derive(Debug)]
860pub struct RangeFunc {}
861
862impl TableFunctionImpl for RangeFunc {
863    fn call_with_args(&self, args: TableFunctionArgs) -> Result<Arc<dyn TableProvider>> {
864        let impl_func = GenerateSeriesFuncImpl {
865            name: "range",
866            include_end: false,
867        };
868        impl_func.call_with_args(args)
869    }
870}
871
872#[cfg(test)]
873mod generate_series_tests {
874    use std::sync::Arc;
875
876    use arrow::datatypes::{DataType, Field, Schema};
877    use datafusion_common::Result;
878    use datafusion_physical_plan::memory::LazyBatchGenerator;
879
880    use crate::generate_series::GenericSeriesState;
881
882    #[test]
883    fn test_generic_series_state_reset() -> Result<()> {
884        let schema = Arc::new(Schema::new(vec![Field::new("a", DataType::Int64, false)]));
885        let mut state = GenericSeriesState::<i64> {
886            schema,
887            start: 1,
888            end: 5,
889            step: 1,
890            current: 1,
891            finished: false,
892            batch_size: 8192,
893            include_end: true,
894            name: "test",
895        };
896        let batch = state.generate_next_batch()?.expect("missing batch");
897
898        let state_reset = state.reset_state();
899        let reset_batch = state_reset
900            .write()
901            .generate_next_batch()?
902            .expect("missing reset batch");
903
904        assert_eq!(batch, reset_batch);
905
906        Ok(())
907    }
908
909    #[test]
910    fn test_generic_series_state_reset_after_overflow() -> Result<()> {
911        let schema = Arc::new(Schema::new(vec![Field::new("a", DataType::Int64, false)]));
912        let mut state = GenericSeriesState::<i64> {
913            schema,
914            start: i64::MAX - 1,
915            end: i64::MAX,
916            step: 2,
917            current: i64::MAX - 1,
918            finished: false,
919            batch_size: 8192,
920            include_end: true,
921            name: "test",
922        };
923        let batch = state.generate_next_batch()?.expect("missing batch");
924        assert!(state.generate_next_batch()?.is_none());
925
926        let state_reset = state.reset_state();
927        let reset_batch = state_reset
928            .write()
929            .generate_next_batch()?
930            .expect("missing reset batch");
931
932        assert_eq!(batch, reset_batch);
933
934        Ok(())
935    }
936}