Skip to content

Commit

Permalink
[CALCITE-6814] Support weekofyear fucntions for Hive
Browse files Browse the repository at this point in the history
  • Loading branch information
xuyu committed Feb 4, 2025
1 parent f10d0ce commit b049643
Show file tree
Hide file tree
Showing 7 changed files with 123 additions and 37 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -337,6 +337,7 @@
import static org.apache.calcite.sql.fun.SqlLibraryOperators.UNIX_SECONDS;
import static org.apache.calcite.sql.fun.SqlLibraryOperators.URL_DECODE;
import static org.apache.calcite.sql.fun.SqlLibraryOperators.URL_ENCODE;
import static org.apache.calcite.sql.fun.SqlLibraryOperators.WEEKOFYEAR;
import static org.apache.calcite.sql.fun.SqlLibraryOperators.XML_TRANSFORM;
import static org.apache.calcite.sql.fun.SqlStdOperatorTable.ABS;
import static org.apache.calcite.sql.fun.SqlStdOperatorTable.ACOS;
Expand Down Expand Up @@ -528,7 +529,7 @@
import static org.apache.calcite.sql.fun.SqlStdOperatorTable.UNARY_PLUS;
import static org.apache.calcite.sql.fun.SqlStdOperatorTable.UPPER;
import static org.apache.calcite.sql.fun.SqlStdOperatorTable.USER;
import static org.apache.calcite.sql.fun.SqlStdOperatorTable.VARIANTNULL;
import static org.apache.calcite.sql.fun.SqlStdOperatorTable.VARIANTNULL;;
import static org.apache.calcite.util.ReflectUtil.isStatic;

import static java.util.Objects.requireNonNull;
Expand Down Expand Up @@ -960,6 +961,8 @@ void populate2() {
BuiltInMethod.DATE_FROM_UNIX_DATE.method, NullPolicy.STRICT);
defineMethod(UNIX_DATE, BuiltInMethod.UNIX_DATE.method,
NullPolicy.STRICT);
defineMethod(WEEKOFYEAR, BuiltInMethod.WEEK_OF_YEAR.method,
NullPolicy.STRICT);

// Datetime constructors
defineMethod(DATE, BuiltInMethod.DATE.method, NullPolicy.STRICT);
Expand Down
68 changes: 56 additions & 12 deletions core/src/main/java/org/apache/calcite/runtime/SqlFunctions.java
Original file line number Diff line number Diff line change
Expand Up @@ -93,19 +93,10 @@
import java.text.Normalizer;
import java.text.ParsePosition;
import java.text.SimpleDateFormat;
import java.time.Instant;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.LocalTime;
import java.time.OffsetDateTime;
import java.time.ZoneId;
import java.time.ZoneOffset;
import java.time.ZonedDateTime;
import java.time.format.DateTimeFormatter;
import java.time.format.DateTimeFormatterBuilder;
import java.time.format.DateTimeParseException;
import java.time.format.SignStyle;
import java.time.*;
import java.time.format.*;
import java.time.temporal.ChronoField;
import java.time.temporal.TemporalAccessor;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Base64;
Expand All @@ -114,6 +105,7 @@
import java.util.Collections;
import java.util.Comparator;
import java.util.Date;
import java.util.GregorianCalendar;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedHashMap;
Expand All @@ -134,6 +126,8 @@
import java.util.regex.Pattern;
import java.util.regex.PatternSyntaxException;

import static java.time.temporal.ChronoField.*;

import static org.apache.calcite.config.CalciteSystemProperty.FUNCTION_LEVEL_CACHE_MAX_SIZE;
import static org.apache.calcite.linq4j.Nullness.castNonNull;
import static org.apache.calcite.util.Static.RESOURCE;
Expand Down Expand Up @@ -5514,6 +5508,56 @@ public static String dayNameWithDate(int date, Locale locale) {
.format(ROOT_DAY_FORMAT.withLocale(locale));
}

/**
* SQL {@code WEEKOFYEAR} function, applied to a String argument.
*
* @param date string of date
* @return The week of the year of the given date
*/
public static @Nullable Integer weekOfYear(String date) {
GregorianCalendar calendar = new GregorianCalendar(TimeZone.getTimeZone("UTC"));
calendar.setGregorianChange(new Date(Long.MIN_VALUE));
calendar.setFirstDayOfWeek(Calendar.MONDAY);
calendar.setMinimalDaysInFirstWeek(4);
try {
LocalDate localDate = valueOf(date.trim());
calendar.setTimeInMillis(localDate.atStartOfDay().toInstant(ZoneOffset.UTC).toEpochMilli());
return calendar.get(Calendar.WEEK_OF_YEAR);
} catch (DateTimeParseException | IllegalArgumentException e) {
return null;
}
}

/**
* Obtains an instance of Date from a text string such as 2021-02-22T09:39:27.
* Other supported formats are "2021-02-22T09:39:27Z", "2021-02-22 09:39:27",
* "2021-02-22T09:39:27+00:00", "2021-02-22". Any time information is simply
* dropped.
*
* @param text the text to parse, not null
* @return The {@code LocalDate} objects parsed from the text
* @throws IllegalArgumentException if the text cannot be parsed into a
* {@code Date}
* @throws NullPointerException if {@code text} is null
*/
public static LocalDate valueOf(final String text) {
String s = Objects.requireNonNull(text).trim();
ParsePosition pos = new ParsePosition(0);
try {
DateTimeFormatter PARSE_FORMATTER =
new DateTimeFormatterBuilder().appendValue(YEAR, 1, 10, SignStyle.NORMAL).appendLiteral('-')
.appendValue(MONTH_OF_YEAR, 1, 2, SignStyle.NORMAL).appendLiteral('-')
.appendValue(DAY_OF_MONTH, 1, 2, SignStyle.NORMAL).toFormatter().withResolverStyle(ResolverStyle.STRICT);
TemporalAccessor t = PARSE_FORMATTER.parseUnresolved(s, pos);
if (pos.getErrorIndex() >= 0) {
throw new DateTimeParseException("Text could not be parsed to date", s, pos.getErrorIndex());
}
return LocalDate.of(t.get(YEAR), t.get(MONTH_OF_YEAR), t.get(DAY_OF_MONTH));
} catch (DateTimeException e) {
throw new IllegalArgumentException("Cannot create date, parsing error");
}
}

/**
* SQL {@code MONTHNAME} function, applied to a TIMESTAMP argument.
*
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -166,6 +166,14 @@ private SqlLibraryOperators() {
OperandTypes.family(SqlTypeFamily.ANY, SqlTypeFamily.DATE,
SqlTypeFamily.DATE));

/** The "WEEKOFYEAR(datetime)" function
* (HIVE) return the week of the year of the given date. */
@LibraryOperator(libraries = {HIVE})
public static final SqlFunction WEEKOFYEAR =
SqlBasicFunction.create("WEEKOFYEAR",
ReturnTypes.INTEGER_FORCE_NULLABLE,
OperandTypes.STRING, SqlFunctionCategory.TIMEDATE);

/** The "CONVERT(type, expr [,style])" function (Microsoft SQL Server).
*
* <p>Syntax:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -480,6 +480,13 @@ public static SqlCall stripSeparator(SqlCall call) {
public static final SqlReturnTypeInference INTEGER_NULLABLE =
INTEGER.andThen(SqlTypeTransforms.TO_NULLABLE);

/**
* Type-inference strategy whereby the result type of a call is a nullable
* INT.
*/
public static final SqlReturnTypeInference INTEGER_FORCE_NULLABLE =
INTEGER.andThen(SqlTypeTransforms.FORCE_NULLABLE);

/**
* Type-inference strategy whereby the result type of a call is a BIGINT.
*/
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -443,6 +443,7 @@ public enum BuiltInMethod {
SHA256(SqlFunctions.class, "sha256", String.class),
SHA512(SqlFunctions.class, "sha512", String.class),
THROW_UNLESS(SqlFunctions.class, "throwUnless", boolean.class, String.class),
WEEK_OF_YEAR(SqlFunctions.class, "weekOfYear", String.class),
COMPRESS(CompressionFunctions.class, "compress", String.class),
URL_DECODE(UrlFunctions.class, "urlDecode", String.class),
URL_ENCODE(UrlFunctions.class, "urlEncode", String.class),
Expand Down
49 changes: 25 additions & 24 deletions site/_docs/reference.md
Original file line number Diff line number Diff line change
Expand Up @@ -1480,31 +1480,31 @@ Not implemented:

### Date/time functions

| Operator syntax | Description
|:------------------------- |:-----------
| LOCALTIME | Returns the current date and time in the session time zone in a value of datatype TIME
| LOCALTIME(precision) | Returns the current date and time in the session time zone in a value of datatype TIME, with *precision* digits of precision
| LOCALTIMESTAMP | Returns the current date and time in the session time zone in a value of datatype TIMESTAMP
| LOCALTIMESTAMP(precision) | Returns the current date and time in the session time zone in a value of datatype TIMESTAMP, with *precision* digits of precision
| CURRENT_TIME | Returns the current time in the session time zone, in a value of datatype TIMESTAMP WITH TIME ZONE
| CURRENT_DATE | Returns the current date in the session time zone, in a value of datatype DATE
| CURRENT_TIMESTAMP | Returns the current date and time in the session time zone, in a value of datatype TIMESTAMP WITH TIME ZONE
| EXTRACT(timeUnit FROM datetime) | Extracts and returns the value of a specified datetime field from a datetime value expression
| FLOOR(datetime TO timeUnit) | Rounds *datetime* down to *timeUnit*
| CEIL(datetime TO timeUnit) | Rounds *datetime* up to *timeUnit*
| YEAR(date) | Equivalent to `EXTRACT(YEAR FROM date)`. Returns an integer.
| QUARTER(date) | Equivalent to `EXTRACT(QUARTER FROM date)`. Returns an integer between 1 and 4.
| MONTH(date) | Equivalent to `EXTRACT(MONTH FROM date)`. Returns an integer between 1 and 12.
| WEEK(date) | Equivalent to `EXTRACT(WEEK FROM date)`. Returns an integer between 1 and 53.
| DAYOFYEAR(date) | Equivalent to `EXTRACT(DOY FROM date)`. Returns an integer between 1 and 366.
| DAYOFMONTH(date) | Equivalent to `EXTRACT(DAY FROM date)`. Returns an integer between 1 and 31.
| DAYOFWEEK(date) | Equivalent to `EXTRACT(DOW FROM date)`. Returns an integer between 1 and 7.
| HOUR(date) | Equivalent to `EXTRACT(HOUR FROM date)`. Returns an integer between 0 and 23.
| MINUTE(date) | Equivalent to `EXTRACT(MINUTE FROM date)`. Returns an integer between 0 and 59.
| SECOND(date) | Equivalent to `EXTRACT(SECOND FROM date)`. Returns an integer between 0 and 59.
| TIMESTAMPADD(timeUnit, integer, datetime) | Returns *datetime* with an interval of (signed) *integer* *timeUnit*s added. Equivalent to `datetime + INTERVAL 'integer' timeUnit`
| Operator syntax | Description
|:---------------------------------------------|:-----------
| LOCALTIME | Returns the current date and time in the session time zone in a value of datatype TIME
| LOCALTIME(precision) | Returns the current date and time in the session time zone in a value of datatype TIME, with *precision* digits of precision
| LOCALTIMESTAMP | Returns the current date and time in the session time zone in a value of datatype TIMESTAMP
| LOCALTIMESTAMP(precision) | Returns the current date and time in the session time zone in a value of datatype TIMESTAMP, with *precision* digits of precision
| CURRENT_TIME | Returns the current time in the session time zone, in a value of datatype TIMESTAMP WITH TIME ZONE
| CURRENT_DATE | Returns the current date in the session time zone, in a value of datatype DATE
| CURRENT_TIMESTAMP | Returns the current date and time in the session time zone, in a value of datatype TIMESTAMP WITH TIME ZONE
| EXTRACT(timeUnit FROM datetime) | Extracts and returns the value of a specified datetime field from a datetime value expression
| FLOOR(datetime TO timeUnit) | Rounds *datetime* down to *timeUnit*
| CEIL(datetime TO timeUnit) | Rounds *datetime* up to *timeUnit*
| YEAR(date) | Equivalent to `EXTRACT(YEAR FROM date)`. Returns an integer.
| QUARTER(date) | Equivalent to `EXTRACT(QUARTER FROM date)`. Returns an integer between 1 and 4.
| MONTH(date) | Equivalent to `EXTRACT(MONTH FROM date)`. Returns an integer between 1 and 12.
| WEEK(date) | Equivalent to `EXTRACT(WEEK FROM date)`. Returns an integer between 1 and 53.
| DAYOFYEAR(date) | Equivalent to `EXTRACT(DOY FROM date)`. Returns an integer between 1 and 366.
| DAYOFMONTH(date) | Equivalent to `EXTRACT(DAY FROM date)`. Returns an integer between 1 and 31.
| DAYOFWEEK(date) | Equivalent to `EXTRACT(DOW FROM date)`. Returns an integer between 1 and 7.
| HOUR(date) | Equivalent to `EXTRACT(HOUR FROM date)`. Returns an integer between 0 and 23.
| MINUTE(date) | Equivalent to `EXTRACT(MINUTE FROM date)`. Returns an integer between 0 and 59.
| SECOND(date) | Equivalent to `EXTRACT(SECOND FROM date)`. Returns an integer between 0 and 59.
| TIMESTAMPADD(timeUnit, integer, datetime) | Returns *datetime* with an interval of (signed) *integer* *timeUnit*s added. Equivalent to `datetime + INTERVAL 'integer' timeUnit`
| TIMESTAMPDIFF(timeUnit, datetime, datetime2) | Returns the (signed) number of *timeUnit* intervals between *datetime* and *datetime2*. Equivalent to `(datetime2 - datetime) timeUnit`
| LAST_DAY(date) | Returns the date of the last day of the month in a value of datatype DATE; For example, it returns DATE'2020-02-29' for both DATE'2020-02-10' and TIMESTAMP'2020-02-10 10:10:10'
| LAST_DAY(date) | Returns the date of the last day of the month in a value of datatype DATE; For example, it returns DATE'2020-02-29' for both DATE'2020-02-10' and TIMESTAMP'2020-02-10 10:10:10'

Calls to niladic functions such as `CURRENT_DATE` do not accept parentheses in
standard SQL. Calls with parentheses, such as `CURRENT_DATE()` are accepted in certain
Expand Down Expand Up @@ -3032,6 +3032,7 @@ In the following:
| b s | UNIX_DATE(date) | Returns the number of days since 1970-01-01
| s | URL_DECODE(string) | Decodes a *string* in 'application/x-www-form-urlencoded' format using a specific encoding scheme, returns original *string* when decoded error
| s | URL_ENCODE(string) | Translates a *string* into 'application/x-www-form-urlencoded' format using a specific encoding scheme
| h | WEEKOFYEAR(string) | Returns the week of the year of the given date, from 1 to 54
| o | XMLTRANSFORM(xml, xslt) | Applies XSLT transform *xslt* to XML string *xml* and returns the result

Note:
Expand Down
22 changes: 22 additions & 0 deletions testkit/src/main/java/org/apache/calcite/test/SqlOperatorTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -5559,6 +5559,28 @@ void testBitGetFunc(SqlOperatorFixture f, String functionName) {
f0.forEachLibrary(libraries, consumer);
}

/** Test case for
* <a href="https://issues.apache.org/jira/browse/CALCITE-6814">[CALCITE-6814]
* Add base64 function (enabled in Hive library)</a>. */
@Test void testWeekOfYear() {
final SqlOperatorFixture f0 = fixture().setFor(SqlLibraryOperators.WEEKOFYEAR);
final Consumer<SqlOperatorFixture> consumer = f -> {
f.checkString("weekofyear('2022-02-01')",
"5",
"INTEGER");
f.checkString("weekofyear('2022-02-01 22:21:23')",
"5",
"INTEGER");
f.checkNull("weekofyear(NULL)");
f.checkNull("weekofyear('2022-22-01')");
f.checkNull("weekofyear('aawwss')");
f.checkNull("weekofyear('2022')");
};
final List<SqlLibrary> libraries =
list(SqlLibrary.HIVE);
f0.forEachLibrary(libraries, consumer);
}

@Test void testToDatePg() {
final SqlOperatorFixture f = fixture().withLibrary(SqlLibrary.POSTGRESQL)
.setFor(SqlLibraryOperators.TO_DATE_PG);
Expand Down

0 comments on commit b049643

Please sign in to comment.