DRILL-8340: Add Additional Date Manipulation Functions (#2689)
diff --git a/contrib/udfs/src/main/java/org/apache/drill/exec/udfs/NearestDateUtils.java b/contrib/udfs/src/main/java/org/apache/drill/exec/udfs/DateConversionUtils.java
similarity index 97%
rename from contrib/udfs/src/main/java/org/apache/drill/exec/udfs/NearestDateUtils.java
rename to contrib/udfs/src/main/java/org/apache/drill/exec/udfs/DateConversionUtils.java
index 449c62e..d0652d2 100644
--- a/contrib/udfs/src/main/java/org/apache/drill/exec/udfs/NearestDateUtils.java
+++ b/contrib/udfs/src/main/java/org/apache/drill/exec/udfs/DateConversionUtils.java
@@ -28,7 +28,7 @@
import java.time.temporal.ChronoUnit;
import java.util.Arrays;
-public class NearestDateUtils {
+public class DateConversionUtils {
/**
* Specifies the time grouping to be used with the nearest date function
*/
@@ -48,7 +48,7 @@
SECOND
}
- private static final Logger logger = LoggerFactory.getLogger(NearestDateUtils.class);
+ private static final Logger logger = LoggerFactory.getLogger(DateConversionUtils.class);
/**
* This function takes a Java LocalDateTime object, and an interval string and returns
diff --git a/contrib/udfs/src/main/java/org/apache/drill/exec/udfs/NearestDateFunctions.java b/contrib/udfs/src/main/java/org/apache/drill/exec/udfs/DateFunctions.java
similarity index 65%
rename from contrib/udfs/src/main/java/org/apache/drill/exec/udfs/NearestDateFunctions.java
rename to contrib/udfs/src/main/java/org/apache/drill/exec/udfs/DateFunctions.java
index f61a216..979d603 100644
--- a/contrib/udfs/src/main/java/org/apache/drill/exec/udfs/NearestDateFunctions.java
+++ b/contrib/udfs/src/main/java/org/apache/drill/exec/udfs/DateFunctions.java
@@ -22,10 +22,13 @@
import org.apache.drill.exec.expr.annotations.FunctionTemplate;
import org.apache.drill.exec.expr.annotations.Output;
import org.apache.drill.exec.expr.annotations.Param;
+import org.apache.drill.exec.expr.holders.IntHolder;
+import org.apache.drill.exec.expr.holders.NullableTimeStampHolder;
import org.apache.drill.exec.expr.holders.TimeStampHolder;
import org.apache.drill.exec.expr.holders.VarCharHolder;
-public class NearestDateFunctions {
+
+public class DateFunctions {
/**
* This function takes two arguments, an input date object, and an interval and returns
@@ -53,7 +56,7 @@
* 15SECOND
* SECOND
*/
- @FunctionTemplate(name = "nearestDate",
+ @FunctionTemplate(names = {"nearestDate","nearest_date"},
scope = FunctionTemplate.FunctionScope.SIMPLE,
nulls = FunctionTemplate.NullHandling.NULL_IF_NULL)
public static class NearestDateFunction implements DrillSimpleFunc {
@@ -77,7 +80,7 @@
String input = org.apache.drill.exec.expr.fn.impl.StringFunctionHelpers.toStringFromUTF8(interval.start, interval.end, interval.buffer);
java.time.LocalDateTime ld = java.time.LocalDateTime.ofInstant(java.time.Instant.ofEpochMilli(inputDate.value), java.time.ZoneId.of("UTC"));
- java.time.LocalDateTime td = org.apache.drill.exec.udfs.NearestDateUtils.getDate(ld, input);
+ java.time.LocalDateTime td = DateConversionUtils.getDate(ld, input);
out.value = td.atZone(java.time.ZoneId.of("UTC")).toInstant().toEpochMilli();
}
}
@@ -108,7 +111,7 @@
* 15SECOND
* SECOND
*/
- @FunctionTemplate(name = "nearestDate",
+ @FunctionTemplate(names = {"nearestDate","nearest_date"},
scope = FunctionTemplate.FunctionScope.SIMPLE,
nulls = FunctionTemplate.NullHandling.NULL_IF_NULL)
public static class NearestDateFunctionWithString implements DrillSimpleFunc {
@@ -140,8 +143,66 @@
java.time.format.DateTimeFormatter formatter = java.time.format.DateTimeFormatter.ofPattern(format);
java.time.LocalDateTime dateTime = java.time.LocalDateTime.parse(inputDate, formatter);
- java.time.LocalDateTime td = org.apache.drill.exec.udfs.NearestDateUtils.getDate(dateTime, intervalString);
+ java.time.LocalDateTime td = DateConversionUtils.getDate(dateTime, intervalString);
out.value = td.atZone(java.time.ZoneId.of("UTC")).toInstant().toEpochMilli();
}
}
+
+ @FunctionTemplate(names = {"yearweek","year_week"},
+ scope = FunctionTemplate.FunctionScope.SIMPLE,
+ nulls = FunctionTemplate.NullHandling.NULL_IF_NULL)
+ public static class YearWeekFunction implements DrillSimpleFunc {
+ @Param
+ VarCharHolder inputHolder;
+
+ @Output
+ IntHolder out;
+
+ @Override
+ public void setup() {
+ // noop
+ }
+
+ @Override
+ public void eval() {
+ String input = org.apache.drill.exec.expr.fn.impl.StringFunctionHelpers.toStringFromUTF8(inputHolder.start, inputHolder.end, inputHolder.buffer);
+ java.time.LocalDateTime dt = org.apache.drill.exec.udfs.DateUtilFunctions.getTimestampFromString(input);
+ int week = dt.get(java.time.temporal.IsoFields.WEEK_OF_WEEK_BASED_YEAR);
+ int year = dt.getYear();
+ out.value = (year * 100) + week;
+ }
+ }
+
+ @FunctionTemplate(names = {"to_timestamp"},
+ scope = FunctionTemplate.FunctionScope.SIMPLE,
+ nulls = FunctionTemplate.NullHandling.NULL_IF_NULL)
+ public static class TimestampFunction implements DrillSimpleFunc {
+ /**
+ * This version of the TO_TIMESTAMP function converts strings into timestamps
+ * without the need for a format string. The function will attempt to determine
+ * the date format automatically. If it cannot, the function will return null.
+ */
+ @Param
+ VarCharHolder inputHolder;
+
+ @Output
+ NullableTimeStampHolder out;
+
+ @Override
+ public void setup() {
+ // noop
+ }
+
+ @Override
+ public void eval() {
+ String input = org.apache.drill.exec.expr.fn.impl.StringFunctionHelpers.toStringFromUTF8(inputHolder.start, inputHolder.end, inputHolder.buffer);
+ java.time.LocalDateTime dt = org.apache.drill.exec.udfs.DateUtilFunctions.getTimestampFromString(input);
+ if (dt != null) {
+ out.value = dt.toEpochSecond(java.time.ZoneOffset.UTC) * 1000;
+ out.isSet = 1;
+ } else {
+ out.isSet = 0;
+ }
+ }
+ }
}
diff --git a/contrib/udfs/src/main/java/org/apache/drill/exec/udfs/DateUtilFunctions.java b/contrib/udfs/src/main/java/org/apache/drill/exec/udfs/DateUtilFunctions.java
new file mode 100644
index 0000000..f6380fa
--- /dev/null
+++ b/contrib/udfs/src/main/java/org/apache/drill/exec/udfs/DateUtilFunctions.java
@@ -0,0 +1,164 @@
+/*
+ * 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.
+ */
+
+package org.apache.drill.exec.udfs;
+
+import org.apache.commons.lang3.StringUtils;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.time.DateTimeException;
+import java.time.LocalDate;
+import java.time.LocalDateTime;
+import java.time.LocalTime;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+public class DateUtilFunctions {
+ private static final Logger logger = LoggerFactory.getLogger(DateUtilFunctions.class);
+ // Date Matcher Regexes
+ // yyyy-mm-dd
+ private static final Pattern DB_DATE = Pattern.compile("^(\\d{4})-(\\d{2})-(\\d{2})$");
+
+ // Matches various dates which use slashes
+ private static final Pattern DATE_SLASH = Pattern.compile("^(\\d{1,2})/(\\d{1,2})/(\\d{4})$");
+
+ // Year first with slashes
+ private static final Pattern LEADING_SLASH_DATE = Pattern.compile("^(\\d{4})/(\\d{1,2})/" +
+ "(\\d{1,2})$");
+
+ private static final Pattern TIMESTAMP_PATTERN = Pattern.compile("^(\\d{4})-(\\d{2})-(\\d{2})" +
+ "(?:T|\\s)(\\d{1,2}):(\\d{1,2}):(\\d{1,2})\\.?(\\d*)");
+
+ /** Parses common date strings and returns a {@link LocalDate} of that string.
+ * If the method is unable to parse the string, an error will be logged.
+ * Supports the following formats:
+ *
+ * <ul>
+ * <li>yyyy-MM-dd</li>
+ * <li>MM/dd/yyyy</li>
+ * <li>M/d/yyyy</li>
+ * <li>yyyy/MM/dd</li>
+ * </ul>
+ *
+ * @param inputString An input string containing a date.
+ * @return A {@link LocalDate} of the input string.
+ */
+ public static LocalDate getDateFromString(String inputString) {
+ return getDateFromString(inputString, false);
+ }
+
+ /** Parses common date strings and returns a {@link LocalDate} of that string.
+ * If the method is unable to parse the string, an error will be logged.
+ * Supports the following formats:
+ *
+ * <ul>
+ * <li>yyyy-MM-dd</li>
+ * <li>MM/dd/yyyy</li>
+ * <li>dd/MM/yyyy</li>
+ * <li>M/d/yyyy</li>
+ * <li>yyyy/MM/dd</li>
+ * </ul>
+ *
+ * If the matcher is unable to convert the string, the function returns null.
+ * @param inputString An input string containing a date.
+ * @param leadingDay True if the format has the day first.
+ * @return A {@link LocalDate} of the input string.
+ */
+ public static LocalDate getDateFromString(String inputString, boolean leadingDay) {
+ int year;
+ int month;
+ int day;
+ if (StringUtils.isEmpty(inputString)) {
+ return null;
+ }
+
+ // Clean up input string:
+ inputString = inputString.trim();
+ Matcher dateMatcher;
+ if (DB_DATE.matcher(inputString).matches()) {
+ dateMatcher = DB_DATE.matcher(inputString);
+ dateMatcher.find();
+ year = Integer.parseInt(dateMatcher.group(1));
+ month = Integer.parseInt(dateMatcher.group(2));
+ day = Integer.parseInt(dateMatcher.group(3));
+ } else if (DATE_SLASH.matcher(inputString).matches()) {
+ dateMatcher = DATE_SLASH.matcher(inputString);
+ dateMatcher.find();
+ year = Integer.parseInt(dateMatcher.group(3));
+ if (leadingDay) {
+ month = Integer.parseInt(dateMatcher.group(2));
+ day = Integer.parseInt(dateMatcher.group(1));
+ } else {
+ month = Integer.parseInt(dateMatcher.group(1));
+ day = Integer.parseInt(dateMatcher.group(2));
+ }
+ } else if (LEADING_SLASH_DATE.matcher(inputString).matches()) {
+ dateMatcher = LEADING_SLASH_DATE.matcher(inputString);
+ dateMatcher.find();
+ year = Integer.parseInt(dateMatcher.group(1));
+ month = Integer.parseInt(dateMatcher.group(2));
+ day = Integer.parseInt(dateMatcher.group(3));
+ } else {
+ logger.warn("Unable to parse date {}.", inputString);
+ return null;
+ }
+ try {
+ return LocalDate.of(year,month,day);
+ } catch (DateTimeException e) {
+ logger.warn("Unable to parse date {}.", inputString);
+ return null;
+ }
+ }
+
+ public static LocalDateTime getTimestampFromString(String inputString) {
+ if (StringUtils.isEmpty(inputString)) {
+ return null;
+ }
+ // Clean up input string:
+ inputString = inputString.trim();
+
+ if (inputString.length() <= 10) {
+ LocalDate localDate = getDateFromString(inputString);
+ if (localDate == null) {
+ logger.warn("Unable to parse date {}.", inputString);
+ return null;
+ }
+ LocalTime localTime = LocalTime.of(0,0,0);
+ return LocalDateTime.of(localDate, localTime);
+ }
+
+ Matcher timestampMatcher = TIMESTAMP_PATTERN.matcher(inputString);
+ if (timestampMatcher.find()) {
+ int year = Integer.parseInt(timestampMatcher.group(1));
+ int month = Integer.parseInt(timestampMatcher.group(2));
+ int day = Integer.parseInt(timestampMatcher.group(3));
+ int hour = Integer.parseInt(timestampMatcher.group(4));
+ int minute = Integer.parseInt(timestampMatcher.group(5));
+ int second = Integer.parseInt(timestampMatcher.group(6));
+ int nanos = 0;
+ if (StringUtils.isNotEmpty(timestampMatcher.group(7))) {
+ nanos = Integer.parseInt(timestampMatcher.group(7)) * 1000000;
+ }
+ return LocalDateTime.of(year,month,day,hour,minute,second,nanos);
+ } else {
+ logger.warn("Unable to parse date {}.", inputString);
+ return null;
+ }
+ }
+}
diff --git a/contrib/udfs/src/test/java/org/apache/drill/exec/udfs/TestNearestDateFunctions.java b/contrib/udfs/src/test/java/org/apache/drill/exec/udfs/TestDateFunctions.java
similarity index 90%
rename from contrib/udfs/src/test/java/org/apache/drill/exec/udfs/TestNearestDateFunctions.java
rename to contrib/udfs/src/test/java/org/apache/drill/exec/udfs/TestDateFunctions.java
index 9d4104d..e6e73e0 100644
--- a/contrib/udfs/src/test/java/org/apache/drill/exec/udfs/TestNearestDateFunctions.java
+++ b/contrib/udfs/src/test/java/org/apache/drill/exec/udfs/TestDateFunctions.java
@@ -37,7 +37,7 @@
import static org.junit.Assert.fail;
@Category({UnlikelyTest.class, SqlFunctionTest.class})
-public class TestNearestDateFunctions extends ClusterTest {
+public class TestDateFunctions extends ClusterTest {
@BeforeClass
public static void setup() throws Exception {
@@ -149,7 +149,7 @@
run(query);
fail();
} catch (DrillRuntimeException e) {
- assertTrue(e.getMessage().contains("[BAD_DATE] is not a valid time statement. Expecting: " + Arrays.asList(NearestDateUtils.TimeInterval.values())));
+ assertTrue(e.getMessage().contains("[BAD_DATE] is not a valid time statement. Expecting: " + Arrays.asList(DateConversionUtils.TimeInterval.values())));
}
}
@@ -194,4 +194,33 @@
q4, q4, q4)
.go();
}
+
+ @Test
+ public void testYearWeek() throws Exception {
+ String query = "SELECT yearweek('2012-04-19') as yw, yearweek(CAST('2012-01-19' AS DATE)) AS " +
+ "yw1, year_week(CAST('2020-12-12' AS TIMESTAMP)) AS yw2" +
+ " FROM (VALUES(1))";
+
+ testBuilder()
+ .sqlQuery(query)
+ .ordered()
+ .baselineColumns("yw", "yw1", "yw2")
+ .baselineValues(201216, 201203, 202050)
+ .go();
+ }
+
+ @Test
+ public void testTimestamp() throws Exception {
+ LocalDateTime ts1 = LocalDateTime.of(2020,2,4,0,0,0);
+ LocalDateTime ts2 = LocalDateTime.of(2012,6,12,12,12,45);
+ String query = "SELECT to_timestamp('2020-02-04') AS ts1," +
+ "to_timestamp('2012-06-12T12:12:45.123Z') AS ts2, to_timestamp('AAAAHHHHH') " +
+ "AS ts3 FROM (VALUES(1))";
+ testBuilder()
+ .sqlQuery(query)
+ .unOrdered()
+ .baselineColumns("ts1", "ts2", "ts3")
+ .baselineValues(ts1, ts2, null)
+ .go();
+ }
}
diff --git a/contrib/udfs/src/test/java/org/apache/drill/exec/udfs/TestDateUtils.java b/contrib/udfs/src/test/java/org/apache/drill/exec/udfs/TestDateUtils.java
new file mode 100644
index 0000000..94ca6a5
--- /dev/null
+++ b/contrib/udfs/src/test/java/org/apache/drill/exec/udfs/TestDateUtils.java
@@ -0,0 +1,73 @@
+/*
+ * 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.
+ */
+
+package org.apache.drill.exec.udfs;
+
+import org.junit.Test;
+
+import java.time.LocalDate;
+import java.time.LocalDateTime;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNull;
+
+public class TestDateUtils {
+
+ @Test
+ public void testDateFromString() {
+ LocalDate testDate = LocalDate.of(2022, 3,14);
+ assertEquals(testDate, DateUtilFunctions.getDateFromString("2022-03-14"));
+ assertEquals(testDate, DateUtilFunctions.getDateFromString("3/14/2022"));
+ assertEquals(testDate, DateUtilFunctions.getDateFromString("14/03/2022", true));
+ assertEquals(testDate, DateUtilFunctions.getDateFromString("2022/3/14"));
+
+ // Test bad dates
+ assertNull(DateUtilFunctions.getDateFromString(null));
+ assertNull(DateUtilFunctions.getDateFromString("1975-13-56"));
+ assertNull(DateUtilFunctions.getDateFromString("1975-1s"));
+ }
+
+ @Test
+ public void testTimestampFromString() {
+ LocalDateTime testNoSecondFragments = LocalDateTime.of(2022,4,19,17,3,46);
+ LocalDateTime test1Digit = LocalDateTime.of(2022,4,19,17,3,46, 1000000);
+ LocalDateTime test2Digit = LocalDateTime.of(2022,4,19,17,3,46, 13000000);
+ LocalDateTime test3Digit = LocalDateTime.of(2022,4,19,17,3,46, 342000000);
+
+ assertEquals(testNoSecondFragments,
+ DateUtilFunctions.getTimestampFromString("2022-04-19 17:03:46"));
+ assertEquals(testNoSecondFragments,
+ DateUtilFunctions.getTimestampFromString("2022-04-19T17:03:46"));
+ assertEquals(testNoSecondFragments,
+ DateUtilFunctions.getTimestampFromString("2022-04-19T17:03:46.000Z"));
+ assertEquals(test1Digit,
+ DateUtilFunctions.getTimestampFromString("2022-04-19T17:03:46.1Z"));
+ assertEquals(test1Digit,
+ DateUtilFunctions.getTimestampFromString("2022-04-19T17:03:46.001Z"));
+ assertEquals(test2Digit,
+ DateUtilFunctions.getTimestampFromString("2022-04-19T17:03:46.13Z"));
+ assertEquals(test2Digit,
+ DateUtilFunctions.getTimestampFromString("2022-04-19T17:03:46.013Z"));
+ assertEquals(test3Digit,
+ DateUtilFunctions.getTimestampFromString("2022-04-19 17:03:46.342Z"));
+
+ // Test bad dates
+ assertNull(DateUtilFunctions.getTimestampFromString(null));
+ assertNull(DateUtilFunctions.getTimestampFromString(""));
+ }
+}
diff --git a/exec/java-exec/src/main/codegen/data/ExtractTypes.tdd b/exec/java-exec/src/main/codegen/data/ExtractTypes.tdd
index 2d48699..20c446b 100644
--- a/exec/java-exec/src/main/codegen/data/ExtractTypes.tdd
+++ b/exec/java-exec/src/main/codegen/data/ExtractTypes.tdd
@@ -17,6 +17,7 @@
#
{
- toTypes: [Second, Minute, Hour, Day, Week, Month, Year],
+ toTypes: [Second, Minute, Hour, Day, Week, Dow, DayOfWeek, Month, Quarter, Doy, DayOfYear, Year,
+ Epoch],
fromTypes: [Date, Time, TimeStamp, Interval, IntervalDay, IntervalYear]
}
diff --git a/exec/java-exec/src/main/codegen/templates/DateIntervalFunctionTemplates/Extract.java b/exec/java-exec/src/main/codegen/templates/DateIntervalFunctionTemplates/Extract.java
index b09956a..3b496ff 100644
--- a/exec/java-exec/src/main/codegen/templates/DateIntervalFunctionTemplates/Extract.java
+++ b/exec/java-exec/src/main/codegen/templates/DateIntervalFunctionTemplates/Extract.java
@@ -38,7 +38,9 @@
<#list extract.fromTypes as fromUnit>
<#list extract.toTypes as toUnit>
<#if fromUnit == "Date" || fromUnit == "Time" || fromUnit == "TimeStamp">
-<#if !(fromUnit == "Time" && (toUnit == "Year" || toUnit == "Month" || toUnit == "Week" || toUnit == "Day"))>
+<#if !(fromUnit == "Time" && (toUnit == "Year" || toUnit == "Quarter" || toUnit == "Month"
+ || toUnit == "Week" || toUnit == "Day" || toUnit == "Epoch"
+ || toUnit == "Doy" || toUnit == "DayOfYear" || toUnit == "Dow" || toUnit == "DayOfWeek"))>
@FunctionTemplate(name = "extract${toUnit}", scope = FunctionTemplate.FunctionScope.SIMPLE,
nulls = FunctionTemplate.NullHandling.NULL_IF_NULL)
public static class ${toUnit}From${fromUnit} implements DrillSimpleFunc {
@@ -67,12 +69,20 @@
out.value = dateTime.getHourOfDay();
<#elseif toUnit = "Day">
out.value = dateTime.getDayOfMonth();
+ <#elseif toUnit = "Dow">
+ out.value = dateTime.getDayOfWeek();
+ <#elseif toUnit = "Doy">
+ out.value = dateTime.getDayOfYear();
<#elseif toUnit = "Week">
out.value = dateTime.getWeekOfWeekyear();
<#elseif toUnit = "Month">
out.value = dateTime.getMonthOfYear();
+ <#elseif toUnit = "Quarter">
+ out.value = ((int) dateTime.getMonthOfYear() / 4) + 1;
<#elseif toUnit = "Year">
out.value = dateTime.getYear();
+ <#elseif toUnit = "Epoch">
+ out.value = dateTime.getMillis();
</#if>
}
}
@@ -95,6 +105,8 @@
<#if fromUnit == "Interval">
<#if toUnit == "Year">
out.value = (in.months / org.apache.drill.exec.vector.DateUtilities.yearsToMonths);
+ <#elseif toUnit == "Quarter">
+ out.value = (in.months / org.apache.drill.exec.vector.DateUtilities.yearsToQuarter);
<#elseif toUnit == "Month">
out.value = (in.months % org.apache.drill.exec.vector.DateUtilities.yearsToMonths);
<#elseif toUnit == "Week">
@@ -115,6 +127,8 @@
out.value = 0;
<#elseif toUnit == "Week">
out.value = 0;
+ <#elseif toUnit == "Quarter">
+ out.value = 0;
<#elseif toUnit == "Day">
out.value = in.days;
<#elseif toUnit == "Hour">
diff --git a/exec/java-exec/src/main/java/org/apache/drill/exec/planner/logical/DrillOptiq.java b/exec/java-exec/src/main/java/org/apache/drill/exec/planner/logical/DrillOptiq.java
index 022a9b8..5fdb1ff 100644
--- a/exec/java-exec/src/main/java/org/apache/drill/exec/planner/logical/DrillOptiq.java
+++ b/exec/java-exec/src/main/java/org/apache/drill/exec/planner/logical/DrillOptiq.java
@@ -570,14 +570,25 @@
// Get the unit of time to be extracted
String timeUnitStr = ((ValueExpressions.QuotedString) args.get(0)).value;
-
- TimeUnit timeUnit = TimeUnit.valueOf(timeUnitStr);
+ TimeUnit timeUnit;
+ // Clean up day of XXX
+ if (timeUnitStr.contentEquals("DAYOFWEEK")) {
+ timeUnit = TimeUnit.DOW;
+ } else if (timeUnitStr.contentEquals("DAYOFYEAR")) {
+ timeUnit = TimeUnit.DOY;
+ } else {
+ timeUnit = TimeUnit.valueOf(timeUnitStr);
+ }
switch (timeUnit) {
case YEAR:
+ case QUARTER:
case MONTH:
case WEEK:
case DAY:
+ case DOW:
+ case DOY:
+ case EPOCH:
case HOUR:
case MINUTE:
case SECOND:
@@ -585,7 +596,9 @@
functionName += functionPostfix;
return FunctionCallFactory.createExpression(functionName, args.subList(1, 2));
default:
- throw new UnsupportedOperationException("extract function supports the following time units: YEAR, MONTH, WEEK, DAY, HOUR, MINUTE, SECOND");
+ throw new UnsupportedOperationException("extract function supports the following " +
+ "time units: YEAR, QUARTER, MONTH, WEEK, DAY, DAYOFWEEK, DAYOFYEAR, EPOCH, HOUR, " +
+ "MINUTE, SECOND");
}
}
case "timestampdiff": {
diff --git a/exec/java-exec/src/main/java/org/apache/drill/exec/planner/sql/TypeInferenceUtils.java b/exec/java-exec/src/main/java/org/apache/drill/exec/planner/sql/TypeInferenceUtils.java
index 44e46bd..5b209db 100644
--- a/exec/java-exec/src/main/java/org/apache/drill/exec/planner/sql/TypeInferenceUtils.java
+++ b/exec/java-exec/src/main/java/org/apache/drill/exec/planner/sql/TypeInferenceUtils.java
@@ -946,11 +946,25 @@
* For Extract and date_part functions, infer the return types based on timeUnit
*/
public static SqlTypeName getSqlTypeNameForTimeUnit(String timeUnitStr) {
- TimeUnit timeUnit = TimeUnit.valueOf(timeUnitStr);
+ TimeUnit timeUnit;
+
+ // Clean up day of XXX
+ if (timeUnitStr.contentEquals("DAYOFWEEK")) {
+ timeUnit = TimeUnit.DOW;
+ } else if (timeUnitStr.contentEquals("DAYOFYEAR")) {
+ timeUnit = TimeUnit.DOY;
+ } else {
+ timeUnit = TimeUnit.valueOf(timeUnitStr);
+ }
+
switch (timeUnit) {
case YEAR:
+ case QUARTER:
case MONTH:
case WEEK:
+ case EPOCH:
+ case DOY:
+ case DOW:
case DAY:
case HOUR:
case MINUTE:
@@ -960,7 +974,8 @@
default:
throw UserException
.functionError()
- .message("extract function supports the following time units: YEAR, MONTH, WEEK, DAY, HOUR, MINUTE, SECOND")
+ .message("extract function supports the following time units: YEAR, QUARTER, MONTH, " +
+ "WEEK, DAY, DAYOFWEEK, DAYOFYEAR, EPOCH, HOUR, MINUTE, SECOND")
.build(logger);
}
}
diff --git a/exec/java-exec/src/test/java/org/apache/drill/exec/fn/impl/TestNewDateFunctions.java b/exec/java-exec/src/test/java/org/apache/drill/exec/fn/impl/TestNewDateFunctions.java
index 5e5fc01..626b6d1 100644
--- a/exec/java-exec/src/test/java/org/apache/drill/exec/fn/impl/TestNewDateFunctions.java
+++ b/exec/java-exec/src/test/java/org/apache/drill/exec/fn/impl/TestNewDateFunctions.java
@@ -142,6 +142,17 @@
}
@Test
+ public void testExtractQuarter() throws Exception {
+ testBuilder()
+ .sqlQuery("SELECT EXTRACT(QUARTER FROM hire_date) AS col FROM cp.`employee.json` WHERE " +
+ "last_name='Nowmer'")
+ .unOrdered()
+ .baselineColumns("col")
+ .baselineValues(4L)
+ .go();
+ }
+
+ @Test
public void testLocalTimestamp() throws Exception {
testBuilder()
.sqlQuery("select extract(day from localtimestamp) = extract(day from current_date) as col from cp.`employee.json` limit 1")
diff --git a/exec/vector/src/main/java/org/apache/drill/exec/vector/DateUtilities.java b/exec/vector/src/main/java/org/apache/drill/exec/vector/DateUtilities.java
index 9090192..4cfc8da 100644
--- a/exec/vector/src/main/java/org/apache/drill/exec/vector/DateUtilities.java
+++ b/exec/vector/src/main/java/org/apache/drill/exec/vector/DateUtilities.java
@@ -36,6 +36,7 @@
public class DateUtilities {
public static final int yearsToMonths = 12;
+ public static final int yearsToQuarter = 4;
public static final int daysToWeeks = 7;
public static final int hoursToMillis = 60 * 60 * 1000;
public static final int minutesToMillis = 60 * 1000;