From add3025e7345daeb2f844524f4ff7940bbad757a Mon Sep 17 00:00:00 2001 From: Moritz Hoffmann Date: Wed, 23 Sep 2026 21:21:16 +0200 Subject: [PATCH] expr: Convert date.rs to the sqlfunc macro `CastDateToTimestamp`, `CastDateToTimestampTz` and `ExtractDate` all read their payload in `call`, so each takes a `&self` receiver. The two casts need `output_type_expr`: their `output_sql_type` built the precision out of the payload, which the derived `CheckedTimestamp` column type cannot express. The expression sets nullability from the input and the macro's unary tail adds only `propagates_nulls && input_type.nullable`, which the non-nullable `Date` input makes the same term. `introduces_nulls` derives false from the non-`Option` output. `ExtractDate` needs no output modifier. Its `output_sql_type` returned `Numeric { max_scale: None }.nullable(input.nullable)`, which is exactly what `Result` derives once the tail is applied. Per-override accounting, for all three: `call` becomes the function body. `preserves_uniqueness = true` and `inverse` carry over on the casts as modifiers, `is_monotone = true` likewise, and `ExtractDate` keeps its payload-dependent `is_monotone` as an expression over `self`. `could_error` stays derived, true for the `Result` outputs, matching impls that never overrode it. `propagates_nulls` was never overridden and derives true from the non-nullable input. The casts' `Display` wrote a fixed string, so `sqlname` reproduces it, and the SQL names differ from the function names. `ExtractDate` formats its payload, so it keeps its hand-written `Display` under `skip_display = true`. The registry reports only `sqlfunc_signature` moving, on all six records. Co-Authored-By: Claude Opus 5 (1M context) --- .../tests/snapshots/func_registry.json | 12 +- .../snapshots/func_registry_digests.json | 2 +- .../tests/snapshots/func_registry_source.json | 12 +- src/expr/src/scalar/func.rs | 3 - src/expr/src/scalar/func/impls/date.rs | 121 ++++++------------ 5 files changed, 50 insertions(+), 100 deletions(-) diff --git a/src/compute-types/tests/snapshots/func_registry.json b/src/compute-types/tests/snapshots/func_registry.json index 5b38c09868616..3d9ec246daee0 100644 --- a/src/compute-types/tests/snapshots/func_registry.json +++ b/src/compute-types/tests/snapshots/func_registry.json @@ -258,10 +258,10 @@ "cast_char_to_string": {"variant":"CastCharToString","propagates_nulls":true,"introduces_nulls":false,"could_error":false,"preserves_uniqueness":true,"is_monotone":false,"is_eliminable_cast":true,"inverse":"cast_string_to_char","input_types":["Char { length: None }:NotNull"],"output_type":"String:NotNull","sqlfunc_signature":"fn(Char<&'a str>) -> &'a str"}, "cast_date_to_mz_timestamp": {"variant":"CastDateToMzTimestamp","propagates_nulls":true,"introduces_nulls":false,"could_error":true,"preserves_uniqueness":true,"is_monotone":true,"is_eliminable_cast":false,"inverse":null,"input_types":["Date:NotNull"],"output_type":"MzTimestamp:NotNull","sqlfunc_signature":"fn(Date) -> Result"}, "cast_date_to_string": {"variant":"CastDateToString","propagates_nulls":true,"introduces_nulls":false,"could_error":false,"preserves_uniqueness":true,"is_monotone":false,"is_eliminable_cast":false,"inverse":"cast_string_to_date","input_types":["Date:NotNull"],"output_type":"String:NotNull","sqlfunc_signature":"fn(Date) -> String"}, - "cast_date_to_timestamp": {"variant":"CastDateToTimestamp","propagates_nulls":true,"introduces_nulls":false,"could_error":true,"preserves_uniqueness":true,"is_monotone":true,"is_eliminable_cast":false,"inverse":"cast_timestamp_to_date","input_types":["Date:NotNull"],"output_type":"Timestamp { precision: None }:NotNull","sqlfunc_signature":null}, - "cast_date_to_timestamp[precision]": {"variant":"CastDateToTimestamp","propagates_nulls":true,"introduces_nulls":false,"could_error":true,"preserves_uniqueness":true,"is_monotone":true,"is_eliminable_cast":false,"inverse":"cast_timestamp_to_date","input_types":["Date:NotNull"],"output_type":"Timestamp { precision: Some(TimestampPrecision(3)) }:NotNull","sqlfunc_signature":null}, - "cast_date_to_timestamp_tz": {"variant":"CastDateToTimestampTz","propagates_nulls":true,"introduces_nulls":false,"could_error":true,"preserves_uniqueness":true,"is_monotone":true,"is_eliminable_cast":false,"inverse":"cast_timestamp_tz_to_date","input_types":["Date:NotNull"],"output_type":"TimestampTz { precision: None }:NotNull","sqlfunc_signature":null}, - "cast_date_to_timestamp_tz[precision]": {"variant":"CastDateToTimestampTz","propagates_nulls":true,"introduces_nulls":false,"could_error":true,"preserves_uniqueness":true,"is_monotone":true,"is_eliminable_cast":false,"inverse":"cast_timestamp_tz_to_date","input_types":["Date:NotNull"],"output_type":"TimestampTz { precision: Some(TimestampPrecision(3)) }:NotNull","sqlfunc_signature":null}, + "cast_date_to_timestamp": {"variant":"CastDateToTimestamp","propagates_nulls":true,"introduces_nulls":false,"could_error":true,"preserves_uniqueness":true,"is_monotone":true,"is_eliminable_cast":false,"inverse":"cast_timestamp_to_date","input_types":["Date:NotNull"],"output_type":"Timestamp { precision: None }:NotNull","sqlfunc_signature":"fn(Date) -> Result, EvalError>"}, + "cast_date_to_timestamp[precision]": {"variant":"CastDateToTimestamp","propagates_nulls":true,"introduces_nulls":false,"could_error":true,"preserves_uniqueness":true,"is_monotone":true,"is_eliminable_cast":false,"inverse":"cast_timestamp_to_date","input_types":["Date:NotNull"],"output_type":"Timestamp { precision: Some(TimestampPrecision(3)) }:NotNull","sqlfunc_signature":"fn(Date) -> Result, EvalError>"}, + "cast_date_to_timestamp_tz": {"variant":"CastDateToTimestampTz","propagates_nulls":true,"introduces_nulls":false,"could_error":true,"preserves_uniqueness":true,"is_monotone":true,"is_eliminable_cast":false,"inverse":"cast_timestamp_tz_to_date","input_types":["Date:NotNull"],"output_type":"TimestampTz { precision: None }:NotNull","sqlfunc_signature":"fn(Date) -> Result>, EvalError>"}, + "cast_date_to_timestamp_tz[precision]": {"variant":"CastDateToTimestampTz","propagates_nulls":true,"introduces_nulls":false,"could_error":true,"preserves_uniqueness":true,"is_monotone":true,"is_eliminable_cast":false,"inverse":"cast_timestamp_tz_to_date","input_types":["Date:NotNull"],"output_type":"TimestampTz { precision: Some(TimestampPrecision(3)) }:NotNull","sqlfunc_signature":"fn(Date) -> Result>, EvalError>"}, "cast_float32_to_float64": {"variant":"CastFloat32ToFloat64","propagates_nulls":true,"introduces_nulls":false,"could_error":false,"preserves_uniqueness":false,"is_monotone":true,"is_eliminable_cast":false,"inverse":"cast_float64_to_float32","input_types":["Float32:NotNull"],"output_type":"Float64:NotNull","sqlfunc_signature":"fn(f32) -> f64"}, "cast_float32_to_int16": {"variant":"CastFloat32ToInt16","propagates_nulls":true,"introduces_nulls":false,"could_error":true,"preserves_uniqueness":false,"is_monotone":true,"is_eliminable_cast":false,"inverse":"cast_int16_to_float32","input_types":["Float32:NotNull"],"output_type":"Int16:NotNull","sqlfunc_signature":"fn(f32) -> Result"}, "cast_float32_to_int32": {"variant":"CastFloat32ToInt32","propagates_nulls":true,"introduces_nulls":false,"could_error":true,"preserves_uniqueness":false,"is_monotone":true,"is_eliminable_cast":false,"inverse":"cast_int32_to_float32","input_types":["Float32:NotNull"],"output_type":"Int32:NotNull","sqlfunc_signature":"fn(f32) -> Result"}, @@ -472,8 +472,8 @@ "degrees": {"variant":"Degrees","propagates_nulls":true,"introduces_nulls":false,"could_error":false,"preserves_uniqueness":false,"is_monotone":false,"is_eliminable_cast":false,"inverse":null,"input_types":["Float64:NotNull"],"output_type":"Float64:NotNull","sqlfunc_signature":"fn(f64) -> f64"}, "exp": {"variant":"Exp","propagates_nulls":true,"introduces_nulls":false,"could_error":true,"preserves_uniqueness":false,"is_monotone":false,"is_eliminable_cast":false,"inverse":null,"input_types":["Float64:NotNull"],"output_type":"Float64:NotNull","sqlfunc_signature":"fn(f64) -> Result"}, "exp_numeric": {"variant":"ExpNumeric","propagates_nulls":true,"introduces_nulls":false,"could_error":true,"preserves_uniqueness":false,"is_monotone":false,"is_eliminable_cast":false,"inverse":null,"input_types":["Numeric { max_scale: None }:NotNull"],"output_type":"Numeric { max_scale: None }:NotNull","sqlfunc_signature":"fn(Numeric) -> Result"}, - "extract_date": {"variant":"ExtractDate","propagates_nulls":true,"introduces_nulls":false,"could_error":true,"preserves_uniqueness":false,"is_monotone":true,"is_eliminable_cast":false,"inverse":null,"input_types":["Date:NotNull"],"output_type":"Numeric { max_scale: None }:NotNull","sqlfunc_signature":null}, - "extract_date[month]": {"variant":"ExtractDate","propagates_nulls":true,"introduces_nulls":false,"could_error":true,"preserves_uniqueness":false,"is_monotone":false,"is_eliminable_cast":false,"inverse":null,"input_types":["Date:NotNull"],"output_type":"Numeric { max_scale: None }:NotNull","sqlfunc_signature":null}, + "extract_date": {"variant":"ExtractDate","propagates_nulls":true,"introduces_nulls":false,"could_error":true,"preserves_uniqueness":false,"is_monotone":true,"is_eliminable_cast":false,"inverse":null,"input_types":["Date:NotNull"],"output_type":"Numeric { max_scale: None }:NotNull","sqlfunc_signature":"fn(Date) -> Result"}, + "extract_date[month]": {"variant":"ExtractDate","propagates_nulls":true,"introduces_nulls":false,"could_error":true,"preserves_uniqueness":false,"is_monotone":false,"is_eliminable_cast":false,"inverse":null,"input_types":["Date:NotNull"],"output_type":"Numeric { max_scale: None }:NotNull","sqlfunc_signature":"fn(Date) -> Result"}, "extract_interval": {"variant":"ExtractInterval","propagates_nulls":true,"introduces_nulls":false,"could_error":true,"preserves_uniqueness":false,"is_monotone":false,"is_eliminable_cast":false,"inverse":null,"input_types":["Interval:NotNull"],"output_type":"Numeric { max_scale: None }:NotNull","sqlfunc_signature":null}, "extract_interval[month]": {"variant":"ExtractInterval","propagates_nulls":true,"introduces_nulls":false,"could_error":true,"preserves_uniqueness":false,"is_monotone":false,"is_eliminable_cast":false,"inverse":null,"input_types":["Interval:NotNull"],"output_type":"Numeric { max_scale: None }:NotNull","sqlfunc_signature":null}, "extract_time": {"variant":"ExtractTime","propagates_nulls":true,"introduces_nulls":false,"could_error":true,"preserves_uniqueness":false,"is_monotone":false,"is_eliminable_cast":false,"inverse":null,"input_types":["Time:NotNull"],"output_type":"Numeric { max_scale: None }:NotNull","sqlfunc_signature":null}, diff --git a/src/compute-types/tests/snapshots/func_registry_digests.json b/src/compute-types/tests/snapshots/func_registry_digests.json index 138a0c4d2886f..6976d77c42ed0 100644 --- a/src/compute-types/tests/snapshots/func_registry_digests.json +++ b/src/compute-types/tests/snapshots/func_registry_digests.json @@ -1,3 +1,3 @@ { - "1": "sha256:5cb848e09c30b7755418e6981c01beb6f5ebd2d9134fd9ffa15257d76ae80f4a" + "1": "sha256:32a2b493356a458eb2b03362699c55f73ad99d456084d470fae8f2062381dae8" } diff --git a/src/compute-types/tests/snapshots/func_registry_source.json b/src/compute-types/tests/snapshots/func_registry_source.json index c86e546c568ca..da9882f221b1e 100644 --- a/src/compute-types/tests/snapshots/func_registry_source.json +++ b/src/compute-types/tests/snapshots/func_registry_source.json @@ -258,10 +258,10 @@ "cast_char_to_string": {"display":"char_to_text","sqlfunc_decl":"#[sqlfunc(sqlname = \"char_to_text\", preserves_uniqueness = true, is_eliminable_cast = true, inverse = super::CastStringToChar {length: None, fail_on_len: false,})] fn cast_char_to_string<'a>(a: Char<&'a str>) -> &'a str","body_fingerprint":"ba6f1e61cfed1bde"}, "cast_date_to_mz_timestamp": {"display":"date_to_mz_timestamp","sqlfunc_decl":"#[sqlfunc(sqlname = \"date_to_mz_timestamp\", preserves_uniqueness = true, is_monotone = true)] fn cast_date_to_mz_timestamp(a: Date) -> Result","body_fingerprint":"a3e1799a8f942589"}, "cast_date_to_string": {"display":"date_to_text","sqlfunc_decl":"#[sqlfunc(sqlname = \"date_to_text\", preserves_uniqueness = true, inverse = super::CastStringToDate)] fn cast_date_to_string(a: Date) -> String","body_fingerprint":"cb0468f6ec8a742b"}, - "cast_date_to_timestamp": {"display":"date_to_timestamp","sqlfunc_decl":null,"body_fingerprint":null}, - "cast_date_to_timestamp[precision]": {"display":"date_to_timestamp","sqlfunc_decl":null,"body_fingerprint":null}, - "cast_date_to_timestamp_tz": {"display":"date_to_timestamp_with_timezone","sqlfunc_decl":null,"body_fingerprint":null}, - "cast_date_to_timestamp_tz[precision]": {"display":"date_to_timestamp_with_timezone","sqlfunc_decl":null,"body_fingerprint":null}, + "cast_date_to_timestamp": {"display":"date_to_timestamp","sqlfunc_decl":"#[sqlfunc(CastDateToTimestamp, sqlname = \"date_to_timestamp\", output_type_expr = SqlScalarType::Timestamp {precision: self.0}.nullable(input_type.nullable), preserves_uniqueness = true, inverse = super::CastTimestampToDate, is_monotone = true)] fn cast_date_to_timestamp(&self, a: Date) -> Result, EvalError>","body_fingerprint":"42d852e9bdf7e4bc"}, + "cast_date_to_timestamp[precision]": {"display":"date_to_timestamp","sqlfunc_decl":"#[sqlfunc(CastDateToTimestamp, sqlname = \"date_to_timestamp\", output_type_expr = SqlScalarType::Timestamp {precision: self.0}.nullable(input_type.nullable), preserves_uniqueness = true, inverse = super::CastTimestampToDate, is_monotone = true)] fn cast_date_to_timestamp(&self, a: Date) -> Result, EvalError>","body_fingerprint":"42d852e9bdf7e4bc"}, + "cast_date_to_timestamp_tz": {"display":"date_to_timestamp_with_timezone","sqlfunc_decl":"#[sqlfunc(CastDateToTimestampTz, sqlname = \"date_to_timestamp_with_timezone\", output_type_expr = SqlScalarType::TimestampTz {precision: self.0}.nullable(input_type.nullable), preserves_uniqueness = true, inverse = super::CastTimestampTzToDate, is_monotone = true)] fn cast_date_to_timestamp_tz(&self, a: Date) -> Result>, EvalError>","body_fingerprint":"326a2da4421933df"}, + "cast_date_to_timestamp_tz[precision]": {"display":"date_to_timestamp_with_timezone","sqlfunc_decl":"#[sqlfunc(CastDateToTimestampTz, sqlname = \"date_to_timestamp_with_timezone\", output_type_expr = SqlScalarType::TimestampTz {precision: self.0}.nullable(input_type.nullable), preserves_uniqueness = true, inverse = super::CastTimestampTzToDate, is_monotone = true)] fn cast_date_to_timestamp_tz(&self, a: Date) -> Result>, EvalError>","body_fingerprint":"326a2da4421933df"}, "cast_float32_to_float64": {"display":"real_to_double","sqlfunc_decl":"#[sqlfunc(sqlname = \"real_to_double\", preserves_uniqueness = false, inverse = super::CastFloat64ToFloat32, is_monotone = true)] fn cast_float32_to_float64(a: f32) -> f64","body_fingerprint":"bc3f020571556877"}, "cast_float32_to_int16": {"display":"real_to_smallint","sqlfunc_decl":"#[sqlfunc(sqlname = \"real_to_smallint\", preserves_uniqueness = false, inverse = super::CastInt16ToFloat32, is_monotone = true)] fn cast_float32_to_int16(a: f32) -> Result","body_fingerprint":"a61b0421f5cc5491"}, "cast_float32_to_int32": {"display":"real_to_integer","sqlfunc_decl":"#[sqlfunc(sqlname = \"real_to_integer\", preserves_uniqueness = false, inverse = super::CastInt32ToFloat32, is_monotone = true)] fn cast_float32_to_int32(a: f32) -> Result","body_fingerprint":"2c382a851af2f38d"}, @@ -472,8 +472,8 @@ "degrees": {"display":"degrees","sqlfunc_decl":"#[sqlfunc] fn degrees(a: f64) -> f64","body_fingerprint":"344837c3ce0d1ae8"}, "exp": {"display":"expf64","sqlfunc_decl":"#[sqlfunc(sqlname = \"expf64\")] fn exp(a: f64) -> Result","body_fingerprint":"9f8f2167f57a7bea"}, "exp_numeric": {"display":"expnumeric","sqlfunc_decl":"#[sqlfunc(sqlname = \"expnumeric\")] fn exp_numeric(mut a: Numeric) -> Result","body_fingerprint":"de3837766f43f5af"}, - "extract_date": {"display":"extract_epoch_d","sqlfunc_decl":null,"body_fingerprint":null}, - "extract_date[month]": {"display":"extract_month_d","sqlfunc_decl":null,"body_fingerprint":null}, + "extract_date": {"display":"extract_epoch_d","sqlfunc_decl":"#[sqlfunc(ExtractDate, skip_display = true, is_monotone = most_significant_unit(self.0))] fn extract_date(&self, a: Date) -> Result","body_fingerprint":"b7a5fb71cbb7611d"}, + "extract_date[month]": {"display":"extract_month_d","sqlfunc_decl":"#[sqlfunc(ExtractDate, skip_display = true, is_monotone = most_significant_unit(self.0))] fn extract_date(&self, a: Date) -> Result","body_fingerprint":"b7a5fb71cbb7611d"}, "extract_interval": {"display":"extract_epoch_iv","sqlfunc_decl":null,"body_fingerprint":null}, "extract_interval[month]": {"display":"extract_month_iv","sqlfunc_decl":null,"body_fingerprint":null}, "extract_time": {"display":"extract_epoch_t","sqlfunc_decl":null,"body_fingerprint":null}, diff --git a/src/expr/src/scalar/func.rs b/src/expr/src/scalar/func.rs index 2029e6f672c1f..b36590a856006 100644 --- a/src/expr/src/scalar/func.rs +++ b/src/expr/src/scalar/func.rs @@ -139,8 +139,6 @@ func_name! { CastArrayToArray => "cast_array_to_array", CastArrayToJsonb => "cast_array_to_jsonb", CastArrayToString => "cast_array_to_string", - CastDateToTimestamp => "cast_date_to_timestamp", - CastDateToTimestampTz => "cast_date_to_timestamp_tz", CastList1ToList2 => "cast_list1_to_list2", CastListToJsonb => "cast_list_to_jsonb", CastListToString => "cast_list_to_string", @@ -167,7 +165,6 @@ func_name! { DateTruncTimestamp => "date_trunc_timestamp", DateTruncTimestampTz => "date_trunc_timestamp_tz", variadic::ErrorIfNull => "error_if_null", - ExtractDate => "extract_date", ExtractInterval => "extract_interval", ExtractTime => "extract_time", ExtractTimestamp => "extract_timestamp", diff --git a/src/expr/src/scalar/func/impls/date.rs b/src/expr/src/scalar/func/impls/date.rs index 689b8fb05489a..323afb56b2334 100644 --- a/src/expr/src/scalar/func/impls/date.rs +++ b/src/expr/src/scalar/func/impls/date.rs @@ -15,12 +15,11 @@ use mz_repr::adt::date::Date; use mz_repr::adt::datetime::DateTimeUnits; use mz_repr::adt::numeric::Numeric; use mz_repr::adt::timestamp::{CheckedTimestamp, DateLike, TimestampPrecision}; -use mz_repr::{RowArena, SqlColumnType, SqlScalarType, strconv}; +use mz_repr::{SqlScalarType, strconv}; use serde::{Deserialize, Serialize}; use crate::EvalError; use crate::func::most_significant_unit; -use crate::scalar::func::EagerUnaryFunc; #[sqlfunc( sqlname = "date_to_text", @@ -46,38 +45,19 @@ fn cast_date_to_string(a: Date) -> String { )] pub struct CastDateToTimestamp(pub Option); -impl EagerUnaryFunc for CastDateToTimestamp { - type Input<'a> = Date; - type Output<'a> = Result, EvalError>; - - fn call<'a>(&self, a: Self::Input<'a>, _temp_storage: &'a RowArena) -> Self::Output<'a> { - let out = - CheckedTimestamp::from_timestamplike(NaiveDate::from(a).and_hms_opt(0, 0, 0).unwrap())?; - let updated = out.round_to_precision(self.0)?; - Ok(updated) - } - - fn output_sql_type(&self, input: SqlColumnType) -> SqlColumnType { - SqlScalarType::Timestamp { precision: self.0 }.nullable(input.nullable) - } - - fn preserves_uniqueness(&self) -> bool { - true - } - - fn inverse(&self) -> Option { - to_unary!(super::CastTimestampToDate) - } - - fn is_monotone(&self) -> bool { - true - } -} - -impl fmt::Display for CastDateToTimestamp { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - f.write_str("date_to_timestamp") - } +#[sqlfunc( + CastDateToTimestamp, + sqlname = "date_to_timestamp", + output_type_expr = SqlScalarType::Timestamp { precision: self.0 }.nullable(input_type.nullable), + preserves_uniqueness = true, + inverse = super::CastTimestampToDate, + is_monotone = true +)] +fn cast_date_to_timestamp(&self, a: Date) -> Result, EvalError> { + let out = + CheckedTimestamp::from_timestamplike(NaiveDate::from(a).and_hms_opt(0, 0, 0).unwrap())?; + let updated = out.round_to_precision(self.0)?; + Ok(updated) } #[derive( @@ -93,41 +73,22 @@ impl fmt::Display for CastDateToTimestamp { )] pub struct CastDateToTimestampTz(pub Option); -impl EagerUnaryFunc for CastDateToTimestampTz { - type Input<'a> = Date; - type Output<'a> = Result>, EvalError>; - - fn call<'a>(&self, a: Self::Input<'a>, _temp_storage: &'a RowArena) -> Self::Output<'a> { - let out = - CheckedTimestamp::from_timestamplike(DateTime::::from_naive_utc_and_offset( - NaiveDate::from(a).and_hms_opt(0, 0, 0).unwrap(), - Utc, - ))?; - let updated = out.round_to_precision(self.0)?; - Ok(updated) - } - - fn output_sql_type(&self, input: SqlColumnType) -> SqlColumnType { - SqlScalarType::TimestampTz { precision: self.0 }.nullable(input.nullable) - } - - fn preserves_uniqueness(&self) -> bool { - true - } - - fn inverse(&self) -> Option { - to_unary!(super::CastTimestampTzToDate) - } - - fn is_monotone(&self) -> bool { - true - } -} - -impl fmt::Display for CastDateToTimestampTz { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - f.write_str("date_to_timestamp_with_timezone") - } +#[sqlfunc( + CastDateToTimestampTz, + sqlname = "date_to_timestamp_with_timezone", + output_type_expr = SqlScalarType::TimestampTz { precision: self.0 } + .nullable(input_type.nullable), + preserves_uniqueness = true, + inverse = super::CastTimestampTzToDate, + is_monotone = true +)] +fn cast_date_to_timestamp_tz(&self, a: Date) -> Result>, EvalError> { + let out = CheckedTimestamp::from_timestamplike(DateTime::::from_naive_utc_and_offset( + NaiveDate::from(a).and_hms_opt(0, 0, 0).unwrap(), + Utc, + ))?; + let updated = out.round_to_precision(self.0)?; + Ok(updated) } pub fn extract_date_inner(units: DateTimeUnits, date: NaiveDate) -> Result { @@ -175,21 +136,13 @@ pub fn extract_date_inner(units: DateTimeUnits, date: NaiveDate) -> Result = Date; - type Output<'a> = Result; - - fn call<'a>(&self, a: Self::Input<'a>, _temp_storage: &'a RowArena) -> Self::Output<'a> { - extract_date_inner(self.0, a.into()) - } - - fn output_sql_type(&self, input: SqlColumnType) -> SqlColumnType { - SqlScalarType::Numeric { max_scale: None }.nullable(input.nullable) - } - - fn is_monotone(&self) -> bool { - most_significant_unit(self.0) - } +#[sqlfunc( + ExtractDate, + skip_display = true, + is_monotone = most_significant_unit(self.0) +)] +fn extract_date(&self, a: Date) -> Result { + extract_date_inner(self.0, a.into()) } impl fmt::Display for ExtractDate {