Repository navigation
expr: Convert the remaining hand-written functions to the sqlfunc macro - #39062
Conversation
34f958f to
8bbbe96
Compare
8bbbe96 to
1bad261
Compare
1bad261 to
14443d5
Compare
mgree
left a comment
There was a problem hiding this comment.
LGTM! Even Claude only has silly comments.
14443d5 to
a0fc9c4
Compare
a0fc9c4 to
6a6e9d9
Compare
6a6e9d9 to
4ea08d0
Compare
4ea08d0 to
e7b8eab
Compare
e7b8eab to
7be7836
Compare
mgree
left a comment
There was a problem hiding this comment.
Looks like a fine refactor to me. I wonder if we need funcname! at all anymore, but not urgent.
| // The hand-written impl declared a nullable output that introduces NULLs and can | ||
| // error, although the body does neither. The properties are kept as declared. |
There was a problem hiding this comment.
Make an issue in linear to fix this later?
There was a problem hiding this comment.
No issue needed: #39814, two PRs up the stack, drops these pins and lets the macro derive introduces_nulls and could_error as false with a non-nullable output. It is kept separate so this PR changes no declared properties.
Posted by Claude Code.
|
On Posted by Claude Code. |
`CastInt16ToNumeric` carries an optional `NumericMaxScale`, so the annotated function takes a `&self` receiver and the macro generates `call` as a method on the existing struct instead of defining a unit struct. The hand-written `Display` wrote the fixed string `smallint_to_numeric` without reading the payload, so `sqlname` reproduces it and the impl is deleted. The macro then generates `FuncName` with `NAME` from the annotated function's name, which is the `"cast_int16_to_numeric"` the manual `func_name!` entry declared, so that entry is deleted to avoid a duplicate impl. Every method the deleted `EagerUnaryFunc` impl overrode is accounted for: * `call`: the annotated function's body, unchanged. * `output_sql_type`: expressed as `output_type_expr`, which reads `self.0` and the `input_type` binding. The generated tail ORs in `propagates_nulls && input_type.nullable`, and `propagates_nulls` derives to `true` from the non-nullable `i16` input, so the result is the same `nullable(input_type.nullable)` the deleted body returned. * `could_error`: carried across as `self.0.is_some()`. The derived default reads the `Result` output and would widen it to an unconditional `true`. * `inverse`: carried across. * `is_monotone`: carried across. `introduces_nulls` is not overridden: `Result<Numeric, EvalError>` derives `false`, which is what the impl inherited. `propagates_nulls` and `preserves_uniqueness` were not overridden and keep deriving as before. The registry snapshot moves only in its provenance fields: `sqlfunc_signature`, `sqlfunc_decl` and `body_fingerprint` go from null to populated for both the unscaled and the scaled `cast_int16_to_numeric` record. No declared property changed, so the digest is regenerated in place without bumping `LIR_VERSION`. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
`PadChar` reads its `length` field in both the call and the output type, so it takes a `&self` receiver and an `output_type_expr`. The expression collapses to what the hand-written `output_sql_type` returned: it sets nullability from the input, and the macro's tail adds only `propagates_nulls && input_type.nullable`, which the non-nullable `&str` input makes the same term. `introduces_nulls` derives false from the `Char<String>` output. `could_error` stays derived, false for a non-`Result` output, matching an impl that never overrode it. The `Display` impl wrote a fixed string, so `sqlname` reproduces it. Note that the SQL name is `padchar` while the function is `pad_char`, so the `func_name!` entry the macro replaces keeps its own spelling. The registry reports only `sqlfunc_signature` moving, on both the bounded and unbounded samples. No declared property changed. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
`CastFloat32ToNumeric` carries an optional `NumericMaxScale`, so the annotated function takes a `&self` receiver and the macro generates `call` as a method on the existing struct instead of defining a unit struct. The hand-written `Display` wrote the fixed string `real_to_numeric` without reading the payload, so `sqlname` reproduces it and the impl is deleted. The macro then generates `FuncName` with `NAME` from the annotated function's name, which is the `"cast_float32_to_numeric"` the manual `func_name!` entry declared, so that entry is deleted to avoid a duplicate impl. Every method the deleted `EagerUnaryFunc` impl overrode is accounted for: * `call`: the annotated function's body, unchanged. * `output_sql_type`: expressed as `output_type_expr`, which reads `self.0` and the `input_type` binding. The generated tail ORs in `propagates_nulls && input_type.nullable`, and `propagates_nulls` derives to `true` from the non-nullable `f32` input, so the result is the same `nullable(input_type.nullable)` the deleted body returned. * `inverse`: carried across. * `is_monotone`: carried across. `introduces_nulls` is not overridden: `Result<Numeric, EvalError>` derives `false`, which is what the impl inherited. `could_error` was not overridden and keeps deriving to `true` from the `Result` output, as do `propagates_nulls` and `preserves_uniqueness`. The registry reports only `sqlfunc_signature` moving, on both the unscaled and the scaled `cast_float32_to_numeric` record. No declared property changed. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
`CastFloat64ToNumeric` carries an optional `NumericMaxScale`, so the annotated function takes a `&self` receiver and the macro generates `call` as a method on the existing struct instead of defining a unit struct. The hand-written `Display` wrote the fixed string `double_to_numeric` without reading the payload, so `sqlname` reproduces it and the impl is deleted. The macro then generates `FuncName` with `NAME` from the annotated function's name, which is the `"cast_float64_to_numeric"` the manual `func_name!` entry declared, so that entry is deleted to avoid a duplicate impl. Every method the deleted `EagerUnaryFunc` impl overrode is accounted for: * `call`: the annotated function's body, unchanged. * `output_sql_type`: expressed as `output_type_expr`, which reads `self.0` and the `input_type` binding. The generated tail ORs in `propagates_nulls && input_type.nullable`, and `propagates_nulls` derives to `true` from the non-nullable `f64` input, so the result is the same `nullable(input_type.nullable)` the deleted body returned. * `inverse`: carried across. * `is_monotone`: carried across. `introduces_nulls` is not overridden: `Result<Numeric, EvalError>` derives `false`, which is what the impl inherited. `could_error` was not overridden and keeps deriving to `true` from the `Result` output, as do `propagates_nulls` and `preserves_uniqueness`. The registry reports only `sqlfunc_signature` moving, on both the unscaled and the scaled `cast_float64_to_numeric` record. No declared property changed. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
`CastInt32ToNumeric` carries an optional `NumericMaxScale`, so the annotated function takes a `&self` receiver and the macro generates `call` as a method on the existing struct instead of defining a unit struct. The hand-written `Display` wrote the fixed string `integer_to_numeric` without reading the payload, so `sqlname` reproduces it and the impl is deleted. The macro then generates `FuncName` with `NAME` from the annotated function's name, which is the `"cast_int32_to_numeric"` the manual `func_name!` entry declared, so that entry is deleted to avoid a duplicate impl. Every method the deleted `EagerUnaryFunc` impl overrode is accounted for: * `call`: the annotated function's body, unchanged. * `output_sql_type`: expressed as `output_type_expr`, which reads `self.0` and the `input_type` binding. The generated tail ORs in `propagates_nulls && input_type.nullable`, and `propagates_nulls` derives to `true` from the non-nullable `i32` input, so the result is the same `nullable(input_type.nullable)` the deleted body returned. * `could_error`: carried across as `self.0.is_some()`. The derived default reads the `Result` output and would widen it to an unconditional `true`. * `inverse`: carried across. * `is_monotone`: carried across. `introduces_nulls` is not overridden: `Result<Numeric, EvalError>` derives `false`, which is what the impl inherited. `propagates_nulls` and `preserves_uniqueness` were not overridden and keep deriving as before. The registry reports only `sqlfunc_signature` moving, on both the unscaled and the scaled `cast_int32_to_numeric` record. No declared property changed. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
`CastInt64ToNumeric` carries an optional `NumericMaxScale`, so the annotated function takes a `&self` receiver and the macro generates `call` as a method on the existing struct instead of defining a unit struct. The hand-written `Display` wrote the fixed string `bigint_to_numeric` without reading the payload, so `sqlname` reproduces it and the impl is deleted. The macro then generates `FuncName` with `NAME` from the annotated function's name, which is the `"cast_int64_to_numeric"` the manual `func_name!` entry declared, so that entry is deleted to avoid a duplicate impl. Every method the deleted `EagerUnaryFunc` impl overrode is accounted for: * `call`: the annotated function's body, unchanged. * `output_sql_type`: expressed as `output_type_expr`, which reads `self.0` and the `input_type` binding. The generated tail ORs in `propagates_nulls && input_type.nullable`, and `propagates_nulls` derives to `true` from the non-nullable `i64` input, so the result is the same `nullable(input_type.nullable)` the deleted body returned. * `could_error`: carried across as `self.0.is_some()`. The derived default reads the `Result` output and would widen it to an unconditional `true`. * `inverse`: carried across. * `is_monotone`: carried across. `introduces_nulls` is not overridden: `Result<Numeric, EvalError>` derives `false`, which is what the impl inherited. `propagates_nulls` and `preserves_uniqueness` were not overridden and keep deriving as before. The registry reports only `sqlfunc_signature` moving, on both the unscaled and the scaled `cast_int64_to_numeric` record. No declared property changed. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
`CastUint16ToNumeric` carries an optional `NumericMaxScale`, so the annotated function takes a `&self` receiver and the macro generates `call` as a method on the existing struct instead of defining a unit struct. The hand-written `Display` wrote the fixed string `uint2_to_numeric` without reading the payload, so `sqlname` reproduces it and the impl is deleted. The macro then generates `FuncName` with `NAME` from the annotated function's name, which is the `"cast_uint16_to_numeric"` the manual `func_name!` entry declared, so that entry is deleted to avoid a duplicate impl. Every method the deleted `EagerUnaryFunc` impl overrode is accounted for: * `call`: the annotated function's body, unchanged. * `output_sql_type`: expressed as `output_type_expr`, which reads `self.0` and the `input_type` binding. The generated tail ORs in `propagates_nulls && input_type.nullable`, and `propagates_nulls` derives to `true` from the non-nullable `u16` input, so the result is the same `nullable(input_type.nullable)` the deleted body returned. * `could_error`: carried across as `self.0.is_some()`. The derived default reads the `Result` output and would widen it to an unconditional `true`. * `inverse`: carried across. * `is_monotone`: carried across. `introduces_nulls` is not overridden: `Result<Numeric, EvalError>` derives `false`, which is what the impl inherited. `propagates_nulls` and `preserves_uniqueness` were not overridden and keep deriving as before. The registry reports only `sqlfunc_signature` moving, on both the unscaled and the scaled `cast_uint16_to_numeric` record. No declared property changed. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
`CastUint32ToNumeric` carries an optional `NumericMaxScale`, so the annotated function takes a `&self` receiver and the macro generates `call` as a method on the existing struct instead of defining a unit struct. The hand-written `Display` wrote the fixed string `uint4_to_numeric` without reading the payload, so `sqlname` reproduces it and the impl is deleted. The macro then generates `FuncName` with `NAME` from the annotated function's name, which is the `"cast_uint32_to_numeric"` the manual `func_name!` entry declared, so that entry is deleted to avoid a duplicate impl. Every method the deleted `EagerUnaryFunc` impl overrode is accounted for: * `call`: the annotated function's body, unchanged. * `output_sql_type`: expressed as `output_type_expr`, which reads `self.0` and the `input_type` binding. The generated tail ORs in `propagates_nulls && input_type.nullable`, and `propagates_nulls` derives to `true` from the non-nullable `u32` input, so the result is the same `nullable(input_type.nullable)` the deleted body returned. * `could_error`: carried across as `self.0.is_some()`. The derived default reads the `Result` output and would widen it to an unconditional `true`. * `inverse`: carried across. * `is_monotone`: carried across. `introduces_nulls` is not overridden: `Result<Numeric, EvalError>` derives `false`, which is what the impl inherited. `propagates_nulls` and `preserves_uniqueness` were not overridden and keep deriving as before. The registry reports only `sqlfunc_signature` moving, on both the unscaled and the scaled `cast_uint32_to_numeric` record. No declared property changed. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
`CastUint64ToNumeric` carries an optional `NumericMaxScale`, so the annotated function takes a `&self` receiver and the macro generates `call` as a method on the existing struct instead of defining a unit struct. The hand-written `Display` wrote the fixed string `uint8_to_numeric` without reading the payload, so `sqlname` reproduces it and the impl is deleted. The macro then generates `FuncName` with `NAME` from the annotated function's name, which is the `"cast_uint64_to_numeric"` the manual `func_name!` entry declared, so that entry is deleted to avoid a duplicate impl. Every method the deleted `EagerUnaryFunc` impl overrode is accounted for: * `call`: the annotated function's body, unchanged. * `output_sql_type`: expressed as `output_type_expr`, which reads `self.0` and the `input_type` binding. The generated tail ORs in `propagates_nulls && input_type.nullable`, and `propagates_nulls` derives to `true` from the non-nullable `u64` input, so the result is the same `nullable(input_type.nullable)` the deleted body returned. * `could_error`: carried across as `self.0.is_some()`. The derived default reads the `Result` output and would widen it to an unconditional `true`. * `inverse`: carried across. * `is_monotone`: carried across. `introduces_nulls` is not overridden: `Result<Numeric, EvalError>` derives `false`, which is what the impl inherited. `propagates_nulls` and `preserves_uniqueness` were not overridden and keep deriving as before. The registry reports only `sqlfunc_signature` moving, on both the unscaled and the scaled `cast_uint64_to_numeric` record. No declared property changed. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
`AdjustNumericScale` carries the target `NumericMaxScale`, so the annotated function takes a `&self` receiver and the macro generates `call` as a method on the existing struct instead of defining a unit struct. The hand-written `Display` wrote the fixed string `adjust_numeric_scale` without reading the payload, so `sqlname` reproduces it and the impl is deleted. The macro then generates `FuncName` with `NAME` from the annotated function's name, which is the `"adjust_numeric_scale"` the manual `func_name!` entry declared, so that entry is deleted to avoid a duplicate impl. The deleted `EagerUnaryFunc` impl overrode only two methods: * `call`: the annotated function's body, unchanged. * `output_sql_type`: expressed as `output_type_expr`, which reads `self.0` and the `input_type` binding. The generated tail ORs in `propagates_nulls && input_type.nullable`, and `propagates_nulls` derives to `true` from the non-nullable `Numeric` input, so the result is the same `nullable(input_type.nullable)` the deleted body returned. `introduces_nulls` is not overridden: `Result<Numeric, EvalError>` derives `false`, which is what the impl inherited. `could_error`, `propagates_nulls`, `preserves_uniqueness`, `inverse` and `is_monotone` were not overridden and keep deriving as before. The registry reports only `sqlfunc_signature` moving, on the single `adjust_numeric_scale` record. No declared property changed. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
`CastJsonbToNumeric` carries an optional `NumericMaxScale`, so the annotated function takes a `&self` receiver and the macro generates `call` as a method on the existing struct instead of defining a unit struct. The hand-written `Display` wrote the fixed string `jsonb_to_numeric` without reading the payload, so `sqlname` reproduces it and the impl is deleted. The macro then generates `FuncName` with `NAME` from the annotated function's name, which is the `"cast_jsonb_to_numeric"` the manual `func_name!` entry declared, so that entry is deleted to avoid a duplicate impl. Every method the deleted `EagerUnaryFunc` impl overrode is accounted for: * `call`: the annotated function's body, unchanged. * `output_sql_type`: expressed as `output_type_expr`, which reads `self.0` and the `input_type` binding. The generated tail ORs in `propagates_nulls && input_type.nullable`, and `propagates_nulls` derives to `true` from the non-nullable `JsonbRef<'a>` input, so the result is the same `nullable(input_type.nullable)` the deleted body returned. * `is_monotone`: carried across. `introduces_nulls` is not overridden: `Result<Numeric, EvalError>` derives `false`, which is what the impl inherited. `could_error` was not overridden and keeps deriving to `true` from the `Result` output, as do `propagates_nulls`, `preserves_uniqueness` and `inverse`. The registry reports only `sqlfunc_signature` moving, on both the unscaled and the scaled `cast_jsonb_to_numeric` record. No declared property changed. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
`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<Numeric, EvalError>` 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) <noreply@anthropic.com>
`ExtractTime`, `DatePartTime` and `TimezoneTime` all read their payload in `call`, so each takes a `&self` receiver. None of them needs an output modifier. Each hand-written `output_sql_type` returned the derived output column type with nullability taken from the input, and the macro's unary tail is `output.nullable(nullable || (propagates_nulls && input_type.nullable))`, which for the non-nullable `NaiveTime` input and a non-nullable output collapses to exactly that. Per-override accounting, for all three: `call` becomes the function body, and `output_sql_type` becomes the derived default. Nothing else was overridden, so `could_error` stays derived, true for the two `Result` outputs and false for `TimezoneTime`, and `propagates_nulls` derives true from the non-nullable input. All three `Display` impls format the payload, so they stay hand-written under `skip_display = true`. `TimezoneTime`'s function name collides with the module's free `timezone_time`. The generated method is inherent, so the two coexist, and the body spells the call `self::timezone_time` to say which one it reaches. The registry reports only `sqlfunc_signature` moving, on all five records. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The impl was hand-written because it allocates its result, which the eager trait now covers: a `String` output is pushed into the arena by `OutputDatumType::into_result`, exactly as the body did by hand. The body also checked NULL itself, and under the macro that check has to come from the input type. The input is `ExcludeNull<Datum<'a>>`, because `stringify_datum` takes a raw `Datum` and a bare `Datum` accepts NULL. `ExcludeNull` reports `nullable() == false`, so the derived `propagates_nulls` is true, matching the impl's declaration, and its `try_from_result` turns `Datum::Null` into `Err(Ok(Datum::Null))`, which the evaluation layer returns as `Ok(Datum::Null)`. That is the manual check, so the manual check is gone. `introduces_nulls` derives false from the non-`Option` `String` inside the `Result`, matching the declaration. Per-override accounting: `eval` becomes the function body minus the NULL check. `output_sql_type` returned `String.nullable(input_type.nullable)`, which is what the derived `String` output plus the unary tail gives for a non-nullable input, so no output modifier is needed. `preserves_uniqueness = true` carries over. `propagates_nulls` and `introduces_nulls` are derived as above. `could_error` was not overridden and inherited `LazyUnaryFunc`'s conservative `true`, which the fallible `Result` output derives. `is_monotone` and `is_eliminable_cast` returned the trait defaults and are dropped, as is `inverse`, whose `TODO?` about moving `typeconv` into `expr` now sits on the function. `Display` wrote a fixed string, so `sqlname` reproduces it. The registry reports only `sqlfunc_signature` moving, on the one record. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The impl was hand-written because it allocates its result, which the eager trait now covers: a `String` output is pushed into the arena by `OutputDatumType::into_result`, exactly as the body did by hand. The body also checked NULL itself, and under the macro that check has to come from the input type. The input is `ExcludeNull<Datum<'a>>`, because `stringify_datum` takes a raw `Datum` and a bare `Datum` accepts NULL. `ExcludeNull` reports `nullable() == false`, so the derived `propagates_nulls` is true, matching the impl's declaration, and its `try_from_result` turns `Datum::Null` into `Err(Ok(Datum::Null))`, which the evaluation layer returns as `Ok(Datum::Null)`. That is the manual check, so the manual check is gone. `introduces_nulls` derives false from the non-`Option` `String` inside the `Result`, matching the declaration. Per-override accounting: `eval` becomes the function body minus the NULL check. `output_sql_type` returned `String.nullable(input_type.nullable)`, which is what the derived `String` output plus the unary tail gives for a non-nullable input, so no output modifier is needed. `preserves_uniqueness = true` carries over. `propagates_nulls` and `introduces_nulls` are derived as above. `could_error` was not overridden and inherited `LazyUnaryFunc`'s conservative `true`, which the fallible `Result` output derives. `is_monotone` and `is_eliminable_cast` returned the trait defaults and are dropped, as is `inverse`, whose `TODO?` about `typeconv` now sits on the function. `Display` wrote a fixed string, so `sqlname` reproduces it. With the impl gone the file has no `Display` left, so `std::fmt` and the now unused `Eval`, `LazyUnaryFunc`, `RowArena` and `SqlColumnType` imports go with it. The registry reports only `sqlfunc_signature` moving, on the one record. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Both impls were hand-written because they allocate their result, which the eager trait now covers, and both checked NULL in the body, which under the macro has to come from the input type. `CastMapToString` takes `ExcludeNull<Datum<'a>>`, because `stringify_datum` takes a raw `Datum` and a bare `Datum` accepts NULL. `ExcludeNull` reports `nullable() == false`, so the derived `propagates_nulls` is true, matching the declaration, and its `try_from_result` turns `Datum::Null` into `Err(Ok(Datum::Null))`, which the evaluation layer returns as `Ok(Datum::Null)`. `introduces_nulls` derives false from the `String` inside the `Result`. Its `output_sql_type` returned `String.nullable(input_type.nullable)`, which is what the derived output plus the unary tail gives for a non-nullable input, so it needs no output modifier. `preserves_uniqueness = true` carries over, and `could_error` derives true from the fallible output, matching the `LazyUnaryFunc` default the impl inherited. `MapBuildFromRecordList` takes `DatumList<'a>`, which the body already required by unwrapping the input as a list. `DatumList` reports `nullable() == false` on its own, so no `ExcludeNull` wrapper is needed and the derived `propagates_nulls` is true. It returns `DatumMap<'a>`, because the body always builds a map. The impl declared `introduces_nulls` and `could_error` true and a nullable output, although the body returns NULL for no non-NULL input and cannot fail. The conversion keeps all three as declared: `introduces_nulls = true` and `could_error = true` pin the flags the `DatumMap` output would derive false, and the `output_type_expr` that builds the map type from the payload keeps `nullable(true)`. Per-override accounting beyond the above, for both: `eval` becomes the function body minus the NULL check, and `preserves_uniqueness` (on `MapBuildFromRecordList`), `inverse`, `is_monotone` and `is_eliminable_cast` returned the trait defaults and are dropped. Both `Display` impls wrote a fixed string, so `sqlname` reproduces them, and `map_build` differs from the function name. With no `Display` left the file drops `std::fmt` along with the now unused `Eval`, `LazyUnaryFunc` and `SqlColumnType` imports. The registry reports only `sqlfunc_signature` moving, on both records. No declared property changed. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Both impls checked NULL in the body, which under the macro has to come from the input type. `CastRecordToString` was hand-written because it allocates its result, which the eager trait now covers: a `String` output is pushed into the arena by `OutputDatumType::into_result`. Its input is `ExcludeNull<Datum<'a>>`, because `stringify_datum` takes a raw `Datum` and a bare `Datum` accepts NULL. `ExcludeNull` reports `nullable() == false`, so the derived `propagates_nulls` is true, matching the declaration, and its `try_from_result` turns `Datum::Null` into `Err(Ok(Datum::Null))`, which the evaluation layer returns as `Ok(Datum::Null)`. `introduces_nulls` derives false from the `String` inside the `Result`. `output_sql_type` returned `String.nullable(input_type.nullable)`, which the derived output plus the unary tail gives for a non-nullable input, so no output modifier is needed. `preserves_uniqueness = true` carries over, `could_error` derives true from the fallible output, and `inverse`, `is_monotone` and `is_eliminable_cast` returned the trait defaults and are dropped. The `TODO?` on `inverse` now sits on the function. `Display` wrote a fixed string, so `sqlname` reproduces it. `RecordGet` allocates nothing and returns a field borrowed from its input, so its output is a bare `Datum<'a>` and it takes no arena. Its input is `DatumList<'a>`, which is what the body unwrapped the record to; `DatumList` reports `nullable() == false` on its own, so no `ExcludeNull` wrapper is needed and the derived `propagates_nulls` is true. `RecordGet`'s `output_sql_type` reads the input, so it becomes an `output_type_expr` that returns the selected field's column type. The hand-written version also ORed `input_type.nullable` onto that type, which the unary tail now does, so the expression drops the term. `introduces_nulls` derives true from the bare `Datum` output, which is what the impl declared. The output is a bare `Datum`, from which `could_error` would derive false, so the modifier pins the `true` the impl declared by inheriting `LazyUnaryFunc`'s conservative default. `preserves_uniqueness`, `inverse`, `is_monotone` and `is_eliminable_cast` returned the trait defaults and are dropped, and its `Display` formats the field index, so it stays hand-written under `skip_display = true`. `func_registry_records_sqlfunc_sources` used `record_get` as its example of a function with no recorded source. It now has one, so the assertion moves to `extract_interval`. The registry reports only `sqlfunc_signature` moving, on both records. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
`CastListToString` was hand-written because it allocates its result, which the eager trait now covers: a `String` output is pushed into the arena by `OutputDatumType::into_result`. It also checked NULL in the body, which under the macro has to come from the input type. Its input is `ExcludeNull<Datum<'a>>`, because `stringify_datum` takes a raw `Datum` and a bare `Datum` accepts NULL. `ExcludeNull` reports `nullable() == false`, so the derived `propagates_nulls` is true, matching the declaration, and its `try_from_result` turns `Datum::Null` into `Err(Ok(Datum::Null))`, which the evaluation layer returns as `Ok(Datum::Null)`. `introduces_nulls` derives false from the `String` inside the `Result`. Its remaining overrides: `output_sql_type` returned `String.nullable(input_type.nullable)`, which the derived output plus the unary tail gives for a non-nullable input, so no output modifier is needed. `preserves_uniqueness = true` carries over, `could_error` derives true from the fallible output, matching the `LazyUnaryFunc` default the impl inherited, and `inverse`, `is_monotone` and `is_eliminable_cast` returned the trait defaults and are dropped. The `TODO?` on `inverse` now sits on the function. `Display` wrote a fixed string, so `sqlname` reproduces it. `ListLengthMax` is an `EagerBinaryFunc` that reads `max_layer` in `call`, so it takes a `&self` receiver. Its `output_sql_type` spelled out the macro's own binary tail, minus the non-nullable-position term, which the `Option` output's `nullable` of true subsumes, so it needs no output modifier. Nothing else was overridden: `could_error` and `introduces_nulls` derive true from `Result<Option<i32>, EvalError>`, `propagates_nulls` derives true from the non-nullable inputs, and `is_infix_op`, `is_monotone`, `is_infinity_monotone` and `negate` keep the trait defaults. Its `Display` wrote `list_length_max`, which is the function name, so the generated impl reproduces it without a `sqlname`. The struct doc loses only its now satisfied TODO. `doc/developer/sqlfunc.md` counted the convertible hand-written `LazyUnaryFunc` implementations. Seven of the ten are now converted, so the paragraph names the three that remain, all in `string.rs`. The registry reports only `sqlfunc_signature` moving, on both records. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
All sixteen hand-written `EagerUnaryFunc` impls in the file read their payload in
`call`, so each takes a `&self` receiver and the macro attaches the annotated
function as an inherent method on the existing struct.
Four of them need `output_type_expr`. `CastTimestampToTimestampTz`,
`AdjustTimestampPrecision`, `CastTimestampTzToTimestamp` and
`AdjustTimestampTzPrecision` build the output precision out of `self.to`, which
the derived `CheckedTimestamp` column type cannot express. Each expression sets
nullability from the input, and the unary tail adds only `propagates_nulls &&
input_type.nullable`, which the non-nullable timestamp input makes the same
term. `introduces_nulls` derives false from the non-`Option` output. Their
shared `preserves_uniqueness` body carries over verbatim as a block expression
over `self`.
The other twelve need no output modifier. `Result<Numeric, EvalError>`,
`Result<f64, EvalError>`, `Result<CheckedTimestamp<_>, EvalError>` and `String`
derive `Numeric { max_scale: None }`, `Float64`, `Timestamp`/`TimestampTz` with
`precision: None`, and `String` respectively, which is exactly what each
`output_sql_type` returned once the tail is applied.
Per-override accounting: `call` becomes the function body. `is_monotone` carries
over as a constant on the four precision functions and the two `date_trunc`
functions, and as an expression over `self` on `ExtractTimestamp` and
`ExtractTimestampTz`. `inverse` carries over on the two casts. The two
`AdjustTimestamp*Precision` impls overrode `inverse` with `None`, which is the
trait default, so the override disappears. `could_error` stays derived, true for
the `Result` outputs and false for the two `to_char` functions, matching impls
that never overrode it. `propagates_nulls` was never overridden and derives true
from the non-nullable inputs.
The four precision functions wrote a fixed string in `Display`, so `sqlname`
reproduces it. The other twelve format their payload, so they keep their
hand-written `Display` under `skip_display = true`.
`func_registry_records_sqlfunc_sources` used `extract_interval` as its example of
a function with no recorded source. It now uses `cast_array_to_array`, one of the
compound-type casts generic over an `Eval` implementor, a shape the macro does
not cover.
The registry reports only `sqlfunc_signature` moving, on all twenty-six records.
No declared property changed.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Eleven implementations in `src/expr/src/scalar/func/impls/string.rs` move under `#[sqlfunc]`. The four `impl<E: Eval> LazyUnaryFunc` blocks interleaved among them are generic over a sub-expression type and are untouched. `CastStringToNumeric`, `CastStringToTimestamp` and `CastStringToTimestampTz` read their payload in both `call` and `output_sql_type`, so each takes a `&self` receiver and an `output_type_expr` that builds the scale or precision out of that payload. The expression sets nullability from the input, and the macro's unary tail adds only `propagates_nulls && input_type.nullable`, which the non-nullable `&str` input makes the same term. `introduces_nulls` derives false from the non-`Option` outputs. Their `Display` wrote a fixed string, so `sqlname` reproduces it, and `inverse` carries over as a modifier. `CastStringToChar` and `CastStringToVarChar` need the same shape of `output_type_expr`, plus `could_error = self.fail_on_len && self.length.is_some()`, which a `Result` output would otherwise derive as an unconditional true. `CastStringToVarChar` also keeps `preserves_uniqueness` and `is_eliminable_cast` as expressions over `self.length`. `CastStringToChar`'s `is_eliminable_cast` returned the trait default, so the override goes and its reason moves onto the function. Both format their payload, so they keep their `Display` impls under `skip_display = true`. `IsLikeMatch` and `IsRegexpMatch` need no output modifier: their `output_sql_type` returned `Bool.nullable(input.nullable)`, which is what a `bool` output plus the tail gives. Both read their payload in `call` and format it in `Display`, so they take a `&self` receiver and `skip_display = true`. `CastStringToInt2Vector`, `RegexpMatch` and `RegexpSplitToArray` were hand-written `LazyUnaryFunc` implementations because they allocate into a `RowArena` and check NULL in the body. `EagerUnaryFunc::call` supplies the arena, and the NULL check moves into the input type. The first two take `&'a str`, which is already non-nullable. `RegexpMatch` takes `ExcludeNull<Datum<'a>>`, because `regexp_match_static` wants a raw `Datum` and a bare `Datum` accepts NULL; `ExcludeNull` reports `nullable() == false`, so the derived `propagates_nulls` is true, matching the declaration, and its `try_from_result` turns `Datum::Null` into `Err(Ok(Datum::Null))`, which the evaluation layer returns as `Ok(Datum::Null)`. `CastStringToInt2Vector` returns `Int2Vector<'a>`, whose column type the macro derives, so it needs no output modifier, and it reads no payload, so the macro generates its unit struct. `RegexpMatch` and `RegexpSplitToArray` return a `Datum`, whose column type is not statically known, so each carries an `output_type_expr`. `RegexpSplitToArray` returns NULL only for a NULL input, so it declares `introduces_nulls = false` over the `Datum` default. `RegexpMatch` returns NULL when the pattern does not match, which is what the `Datum` default says. `RegexpReplace` is the one `EagerBinaryFunc`. It reads `regex` and `limit` in `call`, so it takes a `&self` receiver, and its `output_sql_type` spelled out the macro's own binary tail minus the non-nullable-position term, which the non-nullable `&str` inputs make redundant, so it needs no output modifier. Its `Display` reads the pattern, the case sensitivity and the limit, so it keeps its hand-written impl under `skip_display = true`. `doc/developer/sqlfunc.md` named the three convertible `LazyUnaryFunc` implementations that remained. There are none left, so the section now states that the two shapes it describes account for every hand-written implementation under `src/expr/src/scalar/func/`. The registry reports only `sqlfunc_signature` moving, on all sixteen records. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
5cc444b to
a0de7ee
Compare
Fifteen `#[sqlfunc]` functions returned a bare `Datum` although each always produces an array, a list, a map or a JSON value. Each therefore restated its nullability with `introduces_nulls`. They now return `Array<'a>`, `DatumList<'a>` or `DatumMap<'a>`, or an `Option` of one where the function can return NULL, and the macro derives the value. The shared helpers return the typed values too, and the two `jsonb_delete` functions drop their `output_type_expr`. The registry reports only `sqlfunc_signature` moving, on all fifteen records. Based on #39062. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-authored-by: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
…d_from_record_list (#39814) Two functions declared properties more conservatively than their bodies need, inherited from hand-written impls and kept by their conversion to `#[sqlfunc]` in #39062. This drops the pins and lets the macro derive the properties from the output type. * `record_get` returns a field of its input record and never produces an `EvalError`, but it declared `could_error = true`, the `LazyUnaryFunc` default. It now derives `false` from the non-`Result` output. * `map_build_from_record_list` skips NULL records and keys and always returns a map, but it declared `introduces_nulls` and `could_error` true and a nullable output. Both flags now derive `false` from the `DatumMap` output, and the output type is non-nullable. The registry reports exactly these moves. The current `LIR_VERSION` hasn't shipped, so the snapshots are regenerated in place. The sqllogictest files that mention `record_get` show no plan changes, and the sqllogictest shards passed on an earlier version of the stack that carried the `map_build_from_record_list` change. Based on #39105. 🤖 Generated with [Claude Code](https://claude.com/claude-code) --------- Co-authored-by: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Converts the remaining hand-written scalar functions to
#[sqlfunc], one commit per source file or function: theCast*ToNumericcasts (int16,int32,int64,uint16,uint32,uint64,float32,float64,jsonb),PadChar,AdjustNumericScale,CastArrayToString,CastRangeToString, and all ofdate.rs,time.rs,map.rs,record.rs,list.rs,timestamp.rsandstring.rs. Each commit message accounts for every method the deleted impl overrode.No declared property changes. The registry reports only the macro provenance fields moving. Where a hand-written impl declared a property more conservatively than the body needs, the conversion pins the declared value:
could_erroronrecord_get, andintroduces_nulls,could_errorand a nullable output onmap_build_from_record_list. #39814 corrects both.Supersedes #39079 to #39097, which held one conversion each.
Based on #39061.
🤖 Generated with Claude Code