weiqingy commented on code in PR #2409: URL: https://github.com/apache/auron/pull/2409#discussion_r3661487029
########## native-engine/datafusion-ext-functions/src/flink_datetime.rs: ########## @@ -0,0 +1,637 @@ +// Licensed to the Apache Software Foundation (ASF) under one or more +// contributor license agreements. See the NOTICE file distributed with +// this work for additional information regarding copyright ownership. +// The ASF licenses this file to You under the Apache License, Version 2.0 +// (the "License"); you may not use this file except in compliance with +// the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +use std::sync::Arc; + +use arrow::{ + array::{Int64Array, StringArray}, + datatypes::DataType, +}; +use chrono::{DateTime, LocalResult, NaiveDateTime, Offset, TimeZone}; +use chrono_tz::{OffsetComponents, Tz}; +use datafusion::{ + common::{DataFusionError, Result, ScalarValue}, + physical_plan::ColumnarValue, +}; +use datafusion_ext_commons::arrow::cast::cast; + +/// Native implementation of Flink SQL `UNIX_TIMESTAMP(value, format)`: parse a +/// formatted date-time string to a Unix timestamp in seconds, replicating +/// `java.text.SimpleDateFormat` lenient semantics. +/// +/// Arguments are always `[value, chronoFormat, zoneId]` (arity 3). `value` is +/// the column of strings to parse; `chronoFormat` and `zoneId` are literal +/// scalars bound at plan time. The format is a `%`-specifier string built by +/// the JVM-side converter from `%Y %m %d %H %M %S` plus literal characters +/// (`%%` for a literal percent) — it is not the original Java pattern. +/// +/// An unparseable value yields `i64::MIN` (Flink's `Long.MIN_VALUE`), never +/// NULL and never an error; a NULL value yields NULL. Arity, format and +/// timezone problems are hard errors rather than silent defaults, so a plumbing +/// bug cannot surface as silently wrong data. +pub fn flink_unix_timestamp(args: &[ColumnarValue]) -> Result<ColumnarValue> { + if args.len() != 3 { Review Comment: Yes, that's the intent. The arity-3 `[value, chronoFormat, zoneId]` shape is a native contract, not a user-facing signature, and the Java side normalizes Flink's two string-parsing forms onto it: - 1-arg `UNIX_TIMESTAMP(str)`: the converter supplies Flink's default `yyyy-MM-dd HH:mm:ss`. - 2-arg `UNIX_TIMESTAMP(str, fmt)`: `fmt` has to be a literal, and it gets translated from the Java pattern to the `%`-specifier form the Rust parser reads. Non-literal or untranslatable patterns are rejected in `isSupported`, and the whole Calc runs on Flink. - The session time zone is resolved at plan time from `table.local-time-zone` and passed as the third argument. So by the time a call reaches the Rust function all three arguments are bound. Any other arity means the plan was built wrong, not that the user wrote something unusual, which is why it's a hard error rather than a default. And yes, weiqingy/auron#1 is that converter. It opens here once this one merges. On the 0-arg form: it's deliberately not covered, and the converter returns false for arity 0 so those queries fall back to Flink (`testUnixTimestampZeroArgFallsBack` pins that). Two reasons it needs its own design rather than another branch in this function: 1. It's a different function. In 1.18.1 the planner registers the niladic form to `DateTimeUtils.unixTimestamp()`, which is `System.currentTimeMillis() / 1000`, and `FlinkSqlOperatorTable.UNIX_TIMESTAMP` is built with `.notDeterministic()`. So it's re-evaluated per record and never folded to a literal at plan time. It parses nothing. 2. The ext-function path can't express it today. `create_auron_ext_function` returns a `ScalarFunctionImplementation`, and `SimpleScalarUDF::invoke_with_args` forwards only `args.args`, dropping `number_rows`. A 0-arg call would arrive with an empty slice and no way to size its output array, while `ScalarFunctionExpr::evaluate` errors when the returned array length doesn't match the batch row count. Supporting it means either carrying the row count in an argument or using a dedicated expression node, plus a decision on whether the clock is read once per batch or once per row, which is observable. I kept #1863 open to track it, with that reasoning in [this comment](https://github.com/apache/auron/issues/1863#issuecomment-5030293882). Good call on the comment. I've expanded the doc block to say the arity-3 shape is the normalized form of the two string arities, and that the no-argument form isn't routed here. If you'd rather see the 0-arg form land before this merges and it makes the review easier for you, I can pick it up. -- This is an automated message from the Apache Git Service. To respond to the message, please log on to GitHub and use the URL above to go to the specific comment. To unsubscribe, e-mail: [email protected] For queries about this service, please contact Infrastructure at: [email protected]
