Skip to content

Commit 47a44bf

Browse files
authored
[analytics-engine] PPL date/time/timestamp fixes β€” format, overloads, span types, invalid literals (#22045)
* datetime stringify uses space, time has no epoch prefix - ArrowValues: scalar + list temporal cells use space separator; time renders as HH:mm:ss[.frac] (no 1970-01-01 prefix); nanos preserved. - ArrowValues: post-process ISO-T VarChar cells from native CAST -> space. - CastToVarcharRewriter (new): boolean -> varchar emits TRUE/FALSE. - ListAggregateMultiTypeIT: assertions updated for new format. Signed-off-by: Vinay Krishna Pudyodu <vinkrish.neo@gmail.com> * PPL date function gap fixes: week modes, format tokens, tz NULL, cast TIME - week() / week_of_year() default to mode 0 (Sunday-start) instead of ISO. - str_to_date %b parses Jan/Feb/.../Dec to month number. - date_format adds %c (month), %U %u %V %v (week numbers), %P (lowercase AM/PM). - unix_timestamp accepts numeric YYYYMMDDhhmmss literal. - convert_tz returns NULL on out-of-range tz instead of throwing. - cast(<datetime-string> AS TIME) strips date prefix and keeps the time portion. Adds os_week and os_strftime Rust UDFs (rust/src/udf/) wired through OsWeekAdapter and the existing format / parse paths. Signed-off-by: Vinay Krishna Pudyodu <vinkrish.neo@gmail.com> * PPL invalid date literals reject with typed format-hint message Pre-isthmus CastTemporalLiteralValidator and per-adapter validation surface malformed string literals as IllegalArgumentException with the typed message "<type>:<value> in unsupported format, please use '<pattern>'", replacing the opaque Arrow Parser error / StreamException at HTTP 500. Covers: - CAST(<lit> AS DATE/TIME/TIMESTAMP) and TIMESTAMPADD/DIFF string args (CastTemporalLiteralValidator RexShuttle). - DATE / TIME / DATE_FORMAT / TIME_FORMAT / DATE_PART unit-aware operands (per-adapter validation). - HOUR(<bad-time>) and STR_TO_DATE with out-of-range month/day (was silent HTTP 200 with wrong row). DATETIME(<invalid>) folds to NULL TIMESTAMP at plan time per legacy SQL DATETIME-of-invalid semantics. Shared parsing routes through DatetimeLiteralValidator so accept-set and format-hint message stay in one place. Signed-off-by: Vinay Krishna Pudyodu <vinkrish.neo@gmail.com> * Add missing PPL date function overloads Adds the substrait-bind paths for ten function overloads that previously failed isthmus lowering with "Unable to convert call <fn>(<sig>)": - DATETIME(string, tz): plan-time fold for all-literal input (NULL on bad value/tz); column-input rewrites to convert_tz with the source offset stripped. tz bounds tightened to MySQL DATETIME range [-13:59, +14:00]. - now(fsp) / sysdate(fsp): drop FSP arg before mapping to DataFusion's niladic now(). - DATE_ADD / DATE_SUB(TIME, interval) and TIMESTAMPADD(unit, n, TIME): anchor TIME to today UTC, then DATETIME_PLUS with the unit interval. - TIMESTAMPADD standalone: new adapter lowering to DATETIME_PLUS so the call binds without a substrait sig for TIMESTAMPADD itself. - TIMESTAMPDIFF standalone: (to_unixtime(t2) - to_unixtime(t1)) scaled by the out-unit factor; MONTH/QUARTER/YEAR use 30/90/365-day approximations matching legacy SQL plugin. - DATE_PART(unit, TIME) and DATE_PART(unit, bare-time-string): today-UTC anchor so the (string, precision_timestamp<P>) sig binds. - FROM_UNIXTIME(epoch, format): compose date_format around the 1-arg UDF. - SPAN(timestamp, N, 'M'|'q'|'y'): rewrite calendar-unit spans to date_bin with a fixed 1970-01-01 origin. - CONVERT_TZ invalid timestamp literal: plan-time fold to typed NULL. - microsecond(): single CAST to TIMESTAMP(6) so the Β΅s fraction survives date_part (no intermediate TIMESTAMP cast that would clip to ms). Signed-off-by: Vinay Krishna Pudyodu <vinkrish.neo@gmail.com> * PPL span(date) and span(time) preserve column type OpenSearchSchemaBuilder reads each date / date_nanos field's `format` mapping and routes date-only formats to DateOnlyType, time-only formats to TimeOnlyType (new UDT markers). Format detection in DateFormatClassifier mirrors the SQL plugin's OpenSearchDateType (named-format allow-lists + custom-pattern symbol scan). The UDT markers are TIMESTAMP-backed BasicSqlType subclasses β€” substrait wire stays Timestamp(ms) so DateParquetField is unchanged. Only the user-visible schema label flips. Result: - span(<date-typed>, …) returns a date column (not widened to timestamp). - span(<time-typed>, …) returns a time column. - Bucket values render as YYYY-MM-DD / HH:mm:ss (no 00:00:00 suffix, no 1970-01-01 prefix). Signed-off-by: Vinay Krishna Pudyodu <vinkrish.neo@gmail.com> * PPL TIMESTAMP(date, time) combine and HOUR-of-TIME-literal fold Lifted from fix/ai-substrait-call-unlowerable (PR 22014): - TIMESTAMP(<date col>, <time col>): 2-arg combine via from_unixtime(to_unixtime(date) + to_unixtime(time)). - HOUR / MINUTE / SECOND / MICROSECOND on a TIME literal: fold to BIGINT at plan time so the call doesn't need a precision_time substrait sig. PR 22014's other capabilities are already covered (or superseded) by the preceding clusters: TimestampAddAdapter and DatetimeAdapter 2-arg fold by B, TimestampDiffAdapter literal fold by B, FromUnixtimeAdapter 2-arg by B, ConvertTzAdapter all-literal fold by F (cleaner reconciliation). Signed-off-by: Vinay Krishna Pudyodu <vinkrish.neo@gmail.com> * test(sandbox): backfill CAST(time-string AS DATE) rejection test Adds testTimeLiteralCastToDateRejected to CastTemporalLiteralValidatorTests. The rejection path itself was added in cluster C; this is the missing direct test for the time-string-to-DATE case. The two capabilities originally scoped under "cluster G" are landed elsewhere: - CAST cross-type permissiveness β€” already covered by cluster C's plan-time validators; only this test was missing. - span() over date_add-derived expr β€” landed as a sql/api fix on the fix/ai/cluster-g2-datetime-udt-normalize branch (DatetimeUdtNormalizeRule re-aligns RexInputRefs after child UDT normalization). Signed-off-by: Vinay Krishna Pudyodu <vinkrish.neo@gmail.com> * test(sandbox): add DatetimeCoverageIT for cluster-all regression gate End-to-end IT covering query shapes fixed by the cluster-all branch: clusters D, F, C, B, A, plus the cherry-pick from PR 22014. Shapes sourced from tests/parquet/a-date-time-failed/ and tests/parquet/assertion-error-deep-dive/ topic folders. Each test method exercises one shape that was failing pre-fix and passes post-fix. Out of scope (deferred): - Timechart depth-15 planner guard - @timestamp:string schema regression in bin/auto-date-histogram - list() time-only stringification (sandbox-side fix needed) - chart timestamp-format and percentile algorithm shapes Signed-off-by: Vinay Krishna Pudyodu <vinkrish.neo@gmail.com> * test(sandbox): @AwaitsFix on tests that require sql cluster A/D These tests pass when sql/fix/ai/cluster-all (commits 3a172a743 cluster A and 2eebeddc cluster D) is installed; without those commits they fail with substrait schema mismatches or unbound function-call lowerings. Marking @AwaitsFix so the sandbox PR can merge before sql. Once sql PR is merged, this commit should be reverted. - DatetimeCoverageIT: 14 methods (cluster A span tests, cluster B/E/F two-arg DATETIME, microsecond preservation, format token spec, etc.) - DateTimeScalarFunctionsIT.testDateAddMillisecondIntervalOnTimestampColumn - TimestampFunctionIT.testShapeCTimeLiteralFoldsWithTodayUtc - TimestampFunctionIT.testTimeEqualsDateDoesNotCrash - TwoShardCommandIT.testReduceCorrectnessAcrossTwoShards Signed-off-by: Vinay Krishna Pudyodu <vinkrish.neo@gmail.com> * PR comments Signed-off-by: Vinay Krishna Pudyodu <vinkrish.neo@gmail.com> * DateOnly/TimeOnly UDTs carry source-type precision (dateβ†’3, date_nanosβ†’9) The UDT branch in OpenSearchSchemaBuilder.buildLeafType bypassed the precision switch added by #22049, so a date_nanos field with a date-only or time-only mapping format produced TIMESTAMP(0). The schema/parquet-read mismatch surfaced on multi-shard sort as RowConverter "Timestamp(ms) got Timestamp(ns)". Thread the source-type precision through DateOnlyType / TimeOnlyType constructors and append it to the digest so canonicalization keeps date-precision-3 and date_nanos-precision-9 distinct (without the digest change, type-factory caching collapses them). Tests: 5 unit assertions in OpenSearchSchemaBuilderTests pinning the precision contract for plain TIMESTAMP, DateOnlyType, and TimeOnlyType paths over both date and date_nanos sources, plus DateNanosUDTPrecisionIT exercising the 2-shard sort regression end-to-end. Signed-off-by: Vinay Krishna Pudyodu <vinkrish.neo@gmail.com> --------- Signed-off-by: Vinay Krishna Pudyodu <vinkrish.neo@gmail.com>
1 parent 80bddcb commit 47a44bf

51 files changed

Lines changed: 3997 additions & 762 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.
Lines changed: 200 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,200 @@
1+
/*
2+
* SPDX-License-Identifier: Apache-2.0
3+
*
4+
* The OpenSearch Contributors require contributions made to
5+
* this file be licensed under the Apache-2.0 license or a
6+
* compatible open source license.
7+
*/
8+
9+
package org.opensearch.analytics.schema;
10+
11+
import java.util.HashSet;
12+
import java.util.Locale;
13+
import java.util.Set;
14+
15+
/**
16+
* Classifies an OpenSearch {@code date} field's {@code format} as date-only, time-only,
17+
* or full timestamp. Mirrors the SQL plugin's {@code OpenSearchDateType} rules
18+
* (named-format allow-lists + custom-pattern symbol scan); sandbox sits below sql-core in
19+
* the dependency graph so the lists are duplicated here. Format syntax is the OpenSearch
20+
* {@code DateFormatter} grammar β€” components separated by {@code ||}.
21+
*/
22+
public final class DateFormatClassifier {
23+
24+
/** Classification result for a {@code date} mapping's {@code format}. */
25+
public enum Kind {
26+
DATE_ONLY,
27+
TIME_ONLY,
28+
TIMESTAMP
29+
}
30+
31+
// TODO: replace with constants from OpenSearch core's DateFormatters registry once they're
32+
// exposed for external use; today we mirror the well-known names locally to avoid coupling
33+
// to internal APIs.
34+
/** Named formats with year/month/day only. */
35+
private static final Set<String> NAMED_DATE_FORMATS = setOf(
36+
"basic_date",
37+
"basic_ordinal_date",
38+
"date",
39+
"strict_date",
40+
"year_month_day",
41+
"strict_year_month_day",
42+
"ordinal_date",
43+
"strict_ordinal_date",
44+
"week_date",
45+
"strict_week_date",
46+
"weekyear_week_day",
47+
"strict_weekyear_week_day"
48+
);
49+
50+
/** Named formats with hour/minute/second only. */
51+
private static final Set<String> NAMED_TIME_FORMATS = setOf(
52+
"basic_time",
53+
"basic_time_no_millis",
54+
"basic_t_time",
55+
"basic_t_time_no_millis",
56+
"time",
57+
"strict_time",
58+
"time_no_millis",
59+
"strict_time_no_millis",
60+
"hour_minute_second_fraction",
61+
"strict_hour_minute_second_fraction",
62+
"hour_minute_second_millis",
63+
"strict_hour_minute_second_millis",
64+
"hour_minute_second",
65+
"strict_hour_minute_second",
66+
"hour_minute",
67+
"strict_hour_minute",
68+
"hour",
69+
"strict_hour",
70+
"t_time",
71+
"strict_t_time",
72+
"t_time_no_millis",
73+
"strict_t_time_no_millis"
74+
);
75+
76+
/** Named formats forcing TIMESTAMP: full datetime + numeric epochs + incomplete date (year, year_month, …). */
77+
private static final Set<String> NAMED_TIMESTAMP_FORMATS = setOf(
78+
// datetime
79+
"iso8601",
80+
"basic_date_time",
81+
"basic_date_time_no_millis",
82+
"basic_ordinal_date_time",
83+
"basic_ordinal_date_time_no_millis",
84+
"basic_week_date_time",
85+
"strict_basic_week_date_time",
86+
"basic_week_date_time_no_millis",
87+
"strict_basic_week_date_time_no_millis",
88+
"basic_week_date",
89+
"strict_basic_week_date",
90+
"date_optional_time",
91+
"strict_date_optional_time",
92+
"strict_date_optional_time_nanos",
93+
"date_time",
94+
"strict_date_time",
95+
"date_time_no_millis",
96+
"strict_date_time_no_millis",
97+
"date_hour_minute_second_fraction",
98+
"strict_date_hour_minute_second_fraction",
99+
"date_hour_minute_second_millis",
100+
"strict_date_hour_minute_second_millis",
101+
"date_hour_minute_second",
102+
"strict_date_hour_minute_second",
103+
"date_hour_minute",
104+
"strict_date_hour_minute",
105+
"date_hour",
106+
"strict_date_hour",
107+
"ordinal_date_time",
108+
"strict_ordinal_date_time",
109+
"ordinal_date_time_no_millis",
110+
"strict_ordinal_date_time_no_millis",
111+
"week_date_time",
112+
"strict_week_date_time",
113+
"week_date_time_no_millis",
114+
"strict_week_date_time_no_millis",
115+
// numeric epochs
116+
"epoch_millis",
117+
"epoch_second",
118+
"epoch_micros",
119+
// incomplete date β€” can't be downgraded to DATE
120+
"year_month",
121+
"strict_year_month",
122+
"year",
123+
"strict_year",
124+
"week_year",
125+
"week_year_week",
126+
"strict_weekyear_week",
127+
"weekyear",
128+
"strict_weekyear"
129+
);
130+
131+
/** Custom-pattern symbols for time components (matches SQL plugin's CUSTOM_FORMAT_TIME_SYMBOLS). */
132+
private static final String CUSTOM_TIME_SYMBOLS = "nNASsmHkKha";
133+
134+
/** Custom-pattern symbols for date components (matches SQL plugin's CUSTOM_FORMAT_DATE_SYMBOLS). */
135+
private static final String CUSTOM_DATE_SYMBOLS = "FecEWwYqQgdMLDyuG";
136+
137+
private DateFormatClassifier() {}
138+
139+
/** Null / empty maps to {@link Kind#TIMESTAMP} (OpenSearch default). */
140+
public static Kind classify(String format) {
141+
if (format == null || format.isEmpty()) {
142+
return Kind.TIMESTAMP;
143+
}
144+
boolean sawDate = false;
145+
boolean sawTime = false;
146+
for (String component : format.split("\\|\\|")) {
147+
String trimmed = component.trim();
148+
if (trimmed.isEmpty()) {
149+
continue;
150+
}
151+
String stripped = strip8Prefix(trimmed);
152+
String lower = stripped.toLowerCase(Locale.ROOT);
153+
if (NAMED_TIMESTAMP_FORMATS.contains(lower)) {
154+
return Kind.TIMESTAMP;
155+
}
156+
if (NAMED_DATE_FORMATS.contains(lower)) {
157+
sawDate = true;
158+
} else if (NAMED_TIME_FORMATS.contains(lower)) {
159+
sawTime = true;
160+
} else {
161+
// unknown name β†’ treat as custom pattern; scan for symbols
162+
boolean[] flags = scanCustomPattern(stripped);
163+
sawDate |= flags[0];
164+
sawTime |= flags[1];
165+
}
166+
if (sawDate && sawTime) {
167+
return Kind.TIMESTAMP;
168+
}
169+
}
170+
if (sawDate && !sawTime) return Kind.DATE_ONLY;
171+
if (sawTime && !sawDate) return Kind.TIME_ONLY;
172+
return Kind.TIMESTAMP;
173+
}
174+
175+
/** Returns {[hasDateSymbol, hasTimeSymbol]} for a custom DateTimeFormatter pattern. */
176+
private static boolean[] scanCustomPattern(String pattern) {
177+
boolean date = false;
178+
boolean time = false;
179+
for (int i = 0; i < pattern.length(); i++) {
180+
char c = pattern.charAt(i);
181+
if (!date && CUSTOM_DATE_SYMBOLS.indexOf(c) >= 0) date = true;
182+
if (!time && CUSTOM_TIME_SYMBOLS.indexOf(c) >= 0) time = true;
183+
if (date && time) break;
184+
}
185+
return new boolean[] { date, time };
186+
}
187+
188+
/** Strips leading {@code 8} prefix used by OpenSearch's joda compatibility shim. */
189+
private static String strip8Prefix(String s) {
190+
return s.startsWith("8") ? s.substring(1) : s;
191+
}
192+
193+
private static Set<String> setOf(String... values) {
194+
Set<String> set = new HashSet<>();
195+
for (String v : values) {
196+
set.add(v.toLowerCase(Locale.ROOT));
197+
}
198+
return set;
199+
}
200+
}
Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
/*
2+
* SPDX-License-Identifier: Apache-2.0
3+
*
4+
* The OpenSearch Contributors require contributions made to
5+
* this file be licensed under the Apache-2.0 license or a
6+
* compatible open source license.
7+
*/
8+
9+
package org.opensearch.analytics.schema;
10+
11+
import org.apache.calcite.rel.type.RelDataTypeFactory;
12+
import org.apache.calcite.rel.type.RelDataTypeSystem;
13+
import org.apache.calcite.sql.type.BasicSqlType;
14+
import org.apache.calcite.sql.type.SqlTypeName;
15+
16+
import java.util.Locale;
17+
18+
/**
19+
* Marker UDT for an OpenSearch {@code date} column whose mapping {@code format} declares
20+
* only date components. Substrait wire shape stays {@code Timestamp(ms)}; the marker only
21+
* downgrades the result-side type label to {@code date}. Sibling of {@link TimeOnlyType}.
22+
*/
23+
public final class DateOnlyType extends BasicSqlType {
24+
25+
public static final String NAME = "date";
26+
27+
private final boolean nullable;
28+
29+
public DateOnlyType(RelDataTypeSystem typeSystem, boolean nullable, int precision) {
30+
super(typeSystem, SqlTypeName.TIMESTAMP, precision);
31+
this.nullable = nullable;
32+
computeDigest();
33+
}
34+
35+
/** Builds a nullable marker with the precision required to match the parquet read shape. */
36+
public static DateOnlyType nullable(RelDataTypeFactory typeFactory, int precision) {
37+
return new DateOnlyType(typeFactory.getTypeSystem(), true, precision);
38+
}
39+
40+
@Override
41+
public boolean isNullable() {
42+
return nullable;
43+
}
44+
45+
@Override
46+
public BasicSqlType createWithNullability(boolean nullable) {
47+
if (nullable == this.nullable) {
48+
return this;
49+
}
50+
return new DateOnlyType(typeSystem, nullable, getPrecision());
51+
}
52+
53+
@Override
54+
protected void generateTypeString(StringBuilder sb, boolean withDetail) {
55+
// Include precision in the digest — date→3 and date_nanos→9 must be distinct, else
56+
// type-factory canonicalization collapses them.
57+
sb.append(NAME.toUpperCase(Locale.ROOT)).append('(').append(getPrecision()).append(')');
58+
}
59+
}

β€Žsandbox/libs/analytics-api/src/main/java/org/opensearch/analytics/schema/OpenSearchSchemaBuilder.javaβ€Ž

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -240,6 +240,11 @@ public static SqlTypeName mapFieldType(String opensearchType) {
240240
* the same shape they did before.
241241
*/
242242
public static RelDataType buildLeafType(String opensearchType, RelDataTypeFactory typeFactory) {
243+
return buildLeafType(opensearchType, null, typeFactory);
244+
}
245+
246+
/** Format-aware overload: classifies {@code date}/{@code date_nanos} into DateOnly / TimeOnly UDT markers. */
247+
public static RelDataType buildLeafType(String opensearchType, String format, RelDataTypeFactory typeFactory) {
243248
if (opensearchType == null) {
244249
return null;
245250
}
@@ -249,6 +254,17 @@ public static RelDataType buildLeafType(String opensearchType, RelDataTypeFactor
249254
if (BinaryType.NAME.equals(opensearchType)) {
250255
return BinaryType.nullable();
251256
}
257+
if ("date".equals(opensearchType) || "date_nanos".equals(opensearchType)) {
258+
int precision = "date_nanos".equals(opensearchType) ? 9 : 3;
259+
switch (DateFormatClassifier.classify(format)) {
260+
case DATE_ONLY:
261+
return DateOnlyType.nullable(typeFactory, precision);
262+
case TIME_ONLY:
263+
return TimeOnlyType.nullable(typeFactory, precision);
264+
default:
265+
// TIMESTAMP β€” fall through to plain SqlTypeName.TIMESTAMP below
266+
}
267+
}
252268
SqlTypeName sqlType = mapFieldType(opensearchType);
253269
if (sqlType == null) {
254270
return null;
@@ -311,7 +327,8 @@ private static void addLeafFields(
311327
if ("nested".equals(fieldType)) {
312328
continue;
313329
}
314-
RelDataType columnType = buildLeafType(fieldType, typeFactory);
330+
String format = (String) fieldProps.get("format");
331+
RelDataType columnType = buildLeafType(fieldType, format, typeFactory);
315332
if (columnType == null) {
316333
// Unsupported (geo_point/shape/completion/…) or unknown plugin type. Drop the
317334
// column; a query referencing it surfaces a Calcite "column not found" via the
Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
/*
2+
* SPDX-License-Identifier: Apache-2.0
3+
*
4+
* The OpenSearch Contributors require contributions made to
5+
* this file be licensed under the Apache-2.0 license or a
6+
* compatible open source license.
7+
*/
8+
9+
package org.opensearch.analytics.schema;
10+
11+
import org.apache.calcite.rel.type.RelDataTypeFactory;
12+
import org.apache.calcite.rel.type.RelDataTypeSystem;
13+
import org.apache.calcite.sql.type.BasicSqlType;
14+
import org.apache.calcite.sql.type.SqlTypeName;
15+
16+
import java.util.Locale;
17+
18+
/**
19+
* Marker UDT for an OpenSearch {@code date} column whose mapping {@code format} declares
20+
* only time components. Substrait wire shape stays {@code Timestamp(ms)}; the marker only
21+
* downgrades the result-side type label to {@code time}. Sibling of {@link DateOnlyType}.
22+
*/
23+
public final class TimeOnlyType extends BasicSqlType {
24+
25+
public static final String NAME = "time";
26+
27+
private final boolean nullable;
28+
29+
public TimeOnlyType(RelDataTypeSystem typeSystem, boolean nullable, int precision) {
30+
super(typeSystem, SqlTypeName.TIMESTAMP, precision);
31+
this.nullable = nullable;
32+
computeDigest();
33+
}
34+
35+
/** Builds a nullable marker with the precision required to match the parquet read shape. */
36+
public static TimeOnlyType nullable(RelDataTypeFactory typeFactory, int precision) {
37+
return new TimeOnlyType(typeFactory.getTypeSystem(), true, precision);
38+
}
39+
40+
@Override
41+
public boolean isNullable() {
42+
return nullable;
43+
}
44+
45+
@Override
46+
public BasicSqlType createWithNullability(boolean nullable) {
47+
if (nullable == this.nullable) {
48+
return this;
49+
}
50+
return new TimeOnlyType(typeSystem, nullable, getPrecision());
51+
}
52+
53+
@Override
54+
protected void generateTypeString(StringBuilder sb, boolean withDetail) {
55+
// Include precision in the digest — date→3 and date_nanos→9 must be distinct, else
56+
// type-factory canonicalization collapses them.
57+
sb.append(NAME.toUpperCase(Locale.ROOT)).append('(').append(getPrecision()).append(')');
58+
}
59+
}

β€Žsandbox/plugins/analytics-backend-datafusion/rust/src/udf/date_format.rsβ€Ž

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77
*/
88

99
//! `date_format(datetime, format)` β€” render a timestamp via MySQL-style tokens
10-
//! ([`mysql_format`](super::mysql_format)). Returns Utf8; null input β†’ null.
10+
//! ([`os_strftime`](super::os_strftime)). Returns Utf8; null input β†’ null.
1111
1212
use std::any::Any;
1313
use std::sync::Arc;
@@ -23,7 +23,7 @@ use datafusion::logical_expr::{
2323
ColumnarValue, ScalarFunctionArgs, ScalarUDF, ScalarUDFImpl, Signature, Volatility,
2424
};
2525

26-
use super::mysql_format::{format_datetime, FormatMode};
26+
use super::os_strftime::{format_datetime, FormatMode};
2727

2828
pub fn register_all(ctx: &SessionContext) {
2929
ctx.register_udf(ScalarUDF::from(DateFormatUdf::new()));
@@ -159,7 +159,7 @@ mod tests {
159159
use super::*;
160160

161161
#[test]
162-
fn render_scalar_matches_mysql_format() {
162+
fn render_scalar_matches_os_strftime() {
163163
let out = render_at(1_584_268_245_123_456, "%Y-%m-%d %H:%i:%S", FormatMode::Date).unwrap();
164164
assert_eq!(out, "2020-03-15 10:30:45");
165165
}

0 commit comments

Comments
Β (0)