Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion datafusion-pg-functions/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -107,4 +107,4 @@ tokio = { workspace = true, features = ["macros", "rt", "fs"] }
[[test]]
name = "sqllogictest"
path = "tests/sqllogictest.rs"
required-features = ["math", "string"]
required-features = ["math", "string", "datetime"]
10 changes: 5 additions & 5 deletions datafusion-pg-functions/functions.md
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,7 @@ implementation strategy in DataFusion.
| [Bit String](#functions-bitstring) | 9 | 6 | 0 | 0 | 0 | 0 | 0+3 | 3 |
| [Pattern Matching](#functions-matching) | 15 | 7 | 2 | 0 | 2 | 0 | 3+2 | 0 |
| [Data Type Formatting](#functions-formatting) | 10 | 3 | 0 | 0 | 7 | 0 | 0+0 | 0 |
| [Date/Time](#functions-datetime) | 33 | 11 | 0 | 0 | 21 | 0 | 0+1 | 0 |
| [Date/Time](#functions-datetime) | 33 | 11 | 3 | 0 | 18 | 0 | 0+1 | 0 |
| [Enum Support](#functions-enum) | 3 | 0 | 0 | 0 | 0 | 0 | 0+0 | 3 |
| [Geometric](#functions-geometry) | 23 | 1 | 0 | 0 | 0 | 0 | 0+0 | 22 |
| [Network Address](#functions-net) | 14 | 1 | 0 | 0 | 0 | 0 | 0+0 | 13 |
Expand All @@ -85,7 +85,7 @@ implementation strategy in DataFusion.
| [System Administration](#functions-admin) | 105 | 0 | 2 | 0 | 5 | 0 | 0+0 | 98 |
| [Trigger](#functions-trigger) | 3 | 0 | 0 | 0 | 0 | 0 | 0+0 | 3 |
| [Event Trigger](#functions-event-triggers) | 5 | 0 | 0 | 0 | 0 | 0 | 0+0 | 5 |
| **TOTAL** | **768** | **198** | **54** | **4** | **71** | **65** | **21** | **371** |
| **TOTAL** | **768** | **198** | **57** | **4** | **68** | **65** | **21** | **371** |

---

Expand Down Expand Up @@ -386,9 +386,9 @@ implementation strategy in DataFusion.
| `date_trunc` | fn | ✅ | — | 4 | truncate timestamp with time zone to specified units in specified time zone | Native DataFusion |
| `extract` | both | 🚧 | P2 | 6 | extract field from timestamp with time zone | |
| `isfinite` | fn | 🚧 | P2 | 4 | finite timestamp? | |
| `justify_days` | fn | 🚧 | P2 | 1 | promote groups of 30 days to numbers of months | |
| `justify_hours` | fn | 🚧 | P2 | 1 | promote groups of 24 hours to numbers of days | |
| `justify_interval` | fn | 🚧 | P2 | 1 | promote groups of 24 hours to numbers of days and promote groups of 30 days to numbers of months | |
| `justify_days` | fn | 🔧 | — | 1 | promote groups of 30 days to numbers of months | Accepts any Arrow interval unit; returns `Interval(MonthDayNano)`. Time field passed through unchanged. |
| `justify_hours` | fn | 🔧 | — | 1 | promote groups of 24 hours to numbers of days | Accepts any Arrow interval unit; returns `Interval(MonthDayNano)`. Keeps sub-microsecond nanoseconds (PG stores microseconds). |
| `justify_interval` | fn | 🔧 | — | 1 | promote groups of 24 hours to numbers of days and promote groups of 30 days to numbers of months | Accepts any Arrow interval unit; returns `Interval(MonthDayNano)`. Keeps sub-microsecond nanoseconds (PG stores microseconds). |
| `localtime` | fn | 🚧 | P2 | 0 | Current date and time, no time zone | |
| `localtimestamp` | fn | 🚧 | P2 | 0 | Current timestamp, no time zone | |
| `make_date` | fn | ✅ | — | 1 | construct date | Native DataFusion |
Expand Down
34 changes: 29 additions & 5 deletions datafusion-pg-functions/src/datetime.rs
Original file line number Diff line number Diff line change
@@ -1,17 +1,41 @@
//! datetime functions.
//! PostgreSQL date/time functions.
//!
//! This module hosts the UDFs listed in the "Date/Time Functions and
//! Operators" section of [`functions.md`](../functions.md), one file per
//! function:
//!
//! - [`justify_days`] — promote 30-day periods of an interval to months.
//! - [`justify_hours`] — promote 24-hour periods of an interval to days.
//! - [`justify_interval`] — normalize days and time, with sign adjustment.
//!
//! Shared interval input handling lives in the private `interval` module.
//!
//! See the corresponding section of `functions.md` for the catalog of
//! PostgreSQL built-ins in this category and their implementation status.

use datafusion::execution::FunctionRegistry;
use datafusion::logical_expr::ScalarUDF;

mod interval;
pub mod justify_days;
pub mod justify_hours;
pub mod justify_interval;

/// Register every PostgreSQL built-in UDF in the datetime category against
/// `registry`.
///
/// Returns the number of UDFs that were registered.
pub fn register(_registry: &mut dyn FunctionRegistry) -> usize {
let _udfs: Vec<ScalarUDF> = vec![];
// registry.register_udf(...);
0
pub fn register(registry: &mut dyn FunctionRegistry) -> usize {
let udfs: Vec<ScalarUDF> = vec![
justify_days::create_justify_days_udf(),
justify_hours::create_justify_hours_udf(),
justify_interval::create_justify_interval_udf(),
];

let mut count = 0;
for udf in udfs {
let _ = registry.register_udf(udf.into());
count += 1;
}
count
}
89 changes: 89 additions & 0 deletions datafusion-pg-functions/src/datetime/interval.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
//! Shared helpers for the PostgreSQL `interval` UDFs in this module.
//!
//! PostgreSQL has a single `interval` type made of a month, a day and a time
//! field. Arrow has three interval units; these helpers accept all of them,
//! convert each value losslessly to `(months, days, nanoseconds)`, and always
//! produce `Interval(MonthDayNano)`, matching PostgreSQL's one result type.

use std::sync::Arc;

use datafusion::arrow::array::{Array, ArrayRef, AsArray, PrimitiveBuilder, new_null_array};
use datafusion::arrow::datatypes::{
DataType, IntervalDayTimeType, IntervalMonthDayNano, IntervalMonthDayNanoType, IntervalUnit,
IntervalYearMonthType,
};
use datafusion::common::{Result, exec_err};
use datafusion::logical_expr::{Signature, TypeSignature, Volatility};

/// Nanoseconds in one 24-hour day (PostgreSQL `USECS_PER_DAY`, in nanoseconds).
pub(crate) const NANOS_PER_DAY: i64 = 86_400 * 1_000_000_000;

/// Days in one month, as assumed by PostgreSQL (`DAYS_PER_MONTH`).
pub(crate) const DAYS_PER_MONTH: i32 = 30;

/// Nanoseconds in one millisecond, for `Interval(DayTime)` input.
const NANOS_PER_MILLI: i64 = 1_000_000;

/// The error PostgreSQL raises when an interval field overflows.
pub(crate) const OUT_OF_RANGE: &str = "interval out of range";

/// Signature for a function taking one interval of any Arrow unit, or a NULL
/// literal.
pub(crate) fn single_interval_signature() -> Signature {
Signature::one_of(
vec![
TypeSignature::Exact(vec![DataType::Interval(IntervalUnit::MonthDayNano)]),
TypeSignature::Exact(vec![DataType::Interval(IntervalUnit::DayTime)]),
TypeSignature::Exact(vec![DataType::Interval(IntervalUnit::YearMonth)]),
TypeSignature::Exact(vec![DataType::Null]),
],
Volatility::Immutable,
)
}

/// Apply `f(months, days, nanoseconds)` to every non-null element of an
/// interval array, producing an `Interval(MonthDayNano)` array. NULLs stay
/// NULL; an error from `f` fails the whole call.
pub(crate) fn map_interval_array<F>(name: &str, input: &ArrayRef, f: F) -> Result<ArrayRef>
where
F: Fn(i32, i32, i64) -> Result<IntervalMonthDayNano>,
{
let len = input.len();
let mut out = PrimitiveBuilder::<IntervalMonthDayNanoType>::with_capacity(len);
match input.data_type() {
DataType::Null => {
return Ok(new_null_array(
&DataType::Interval(IntervalUnit::MonthDayNano),
len,
));
}
DataType::Interval(IntervalUnit::MonthDayNano) => {
for v in input.as_primitive::<IntervalMonthDayNanoType>().iter() {
match v {
Some(v) => out.append_value(f(v.months, v.days, v.nanoseconds)?),
None => out.append_null(),
}
}
}
DataType::Interval(IntervalUnit::DayTime) => {
for v in input.as_primitive::<IntervalDayTimeType>().iter() {
match v {
Some(v) => {
out.append_value(f(0, v.days, v.milliseconds as i64 * NANOS_PER_MILLI)?)
}
None => out.append_null(),
}
}
}
DataType::Interval(IntervalUnit::YearMonth) => {
for v in input.as_primitive::<IntervalYearMonthType>().iter() {
match v {
Some(months) => out.append_value(f(months, 0, 0)?),
None => out.append_null(),
}
}
}
other => return exec_err!("{name} expects an interval argument, got {other:?}"),
}
Ok(Arc::new(out.finish()))
}
Loading
Loading