Skip to content

Commit 8701367

Browse files
committed
Add GRACE PERIOD to SHOW CREATE MATERIALIZED VIEW
1 parent b0a3bd9 commit 8701367

File tree

4 files changed

+184
-2
lines changed

4 files changed

+184
-2
lines changed

core/trino-main/src/main/java/io/trino/sql/rewrite/ShowQueriesRewrite.java

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -99,6 +99,7 @@
9999
import io.trino.sql.tree.StringLiteral;
100100
import io.trino.sql.tree.TableElement;
101101
import io.trino.sql.tree.Values;
102+
import io.trino.util.DateTimeUtils;
102103

103104
import java.util.ArrayList;
104105
import java.util.Collection;
@@ -561,7 +562,8 @@ private Query showCreateMaterializedView(ShowCreate node)
561562
query,
562563
false,
563564
false,
564-
Optional.empty(), // TODO support GRACE PERIOD
565+
viewDefinition.flatMap(MaterializedViewDefinition::getGracePeriod)
566+
.map(DateTimeUtils::formatDayTimeInterval),
565567
propertyNodes,
566568
viewDefinition.get().getComment())).trim();
567569
return singleValueQuery("Create Materialized View", sql);

core/trino-main/src/main/java/io/trino/util/DateTimeUtils.java

Lines changed: 76 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,11 +17,13 @@
1717
import io.trino.client.IntervalYearMonth;
1818
import io.trino.spi.TrinoException;
1919
import io.trino.spi.type.TimeZoneKey;
20+
import io.trino.sql.tree.IntervalLiteral;
2021
import org.assertj.core.util.VisibleForTesting;
2122
import org.joda.time.DateTime;
2223
import org.joda.time.DurationFieldType;
2324
import org.joda.time.MutablePeriod;
2425
import org.joda.time.Period;
26+
import org.joda.time.PeriodType;
2527
import org.joda.time.ReadWritablePeriod;
2628
import org.joda.time.format.DateTimeFormat;
2729
import org.joda.time.format.DateTimeFormatter;
@@ -34,6 +36,7 @@
3436
import org.joda.time.format.PeriodParser;
3537

3638
import java.time.DateTimeException;
39+
import java.time.Duration;
3740
import java.time.LocalDate;
3841
import java.util.ArrayList;
3942
import java.util.List;
@@ -46,6 +49,12 @@
4649
import static com.google.common.base.Preconditions.checkArgument;
4750
import static io.trino.spi.StandardErrorCode.INVALID_LITERAL;
4851
import static io.trino.sql.tree.IntervalLiteral.IntervalField;
52+
import static io.trino.sql.tree.IntervalLiteral.IntervalField.DAY;
53+
import static io.trino.sql.tree.IntervalLiteral.IntervalField.HOUR;
54+
import static io.trino.sql.tree.IntervalLiteral.IntervalField.MINUTE;
55+
import static io.trino.sql.tree.IntervalLiteral.IntervalField.SECOND;
56+
import static io.trino.sql.tree.IntervalLiteral.Sign.NEGATIVE;
57+
import static io.trino.sql.tree.IntervalLiteral.Sign.POSITIVE;
4958
import static io.trino.util.DateTimeZoneIndex.getChronology;
5059
import static io.trino.util.DateTimeZoneIndex.packDateTimeWithZone;
5160
import static java.lang.Math.toIntExact;
@@ -254,6 +263,69 @@ public static long parseDayTimeInterval(String value, IntervalField startField,
254263
throw invalidQualifier(startField, endField.orElse(startField));
255264
}
256265

266+
public static IntervalLiteral formatDayTimeInterval(Duration duration)
267+
{
268+
long millis = duration.toMillis();
269+
Period period = new Period(Math.abs(millis)).normalizedStandard(PeriodType.dayTime());
270+
271+
IntervalLiteral.IntervalField startField;
272+
Optional<IntervalLiteral.IntervalField> endField;
273+
String value;
274+
275+
if (period.getDays() > 0) {
276+
startField = DAY;
277+
if (period.getSeconds() > 0 || period.getMillis() > 0) {
278+
endField = Optional.of(SECOND);
279+
value = INTERVAL_DAY_SECOND_FORMATTER.print(period);
280+
}
281+
else if (period.getMinutes() > 0) {
282+
endField = Optional.of(MINUTE);
283+
value = INTERVAL_DAY_MINUTE_FORMATTER.print(period);
284+
}
285+
else if (period.getHours() > 0) {
286+
endField = Optional.of(HOUR);
287+
value = INTERVAL_DAY_HOUR_FORMATTER.print(period);
288+
}
289+
else {
290+
endField = Optional.empty();
291+
value = INTERVAL_DAY_FORMATTER.print(period);
292+
}
293+
}
294+
else if (period.getHours() > 0) {
295+
startField = HOUR;
296+
if (period.getSeconds() > 0 || period.getMillis() > 0) {
297+
endField = Optional.of(SECOND);
298+
value = INTERVAL_HOUR_SECOND_FORMATTER.print(period);
299+
}
300+
else if (period.getMinutes() > 0) {
301+
endField = Optional.of(MINUTE);
302+
value = INTERVAL_HOUR_MINUTE_FORMATTER.print(period);
303+
}
304+
else {
305+
endField = Optional.empty();
306+
value = INTERVAL_HOUR_FORMATTER.print(period);
307+
}
308+
}
309+
else if (period.getMinutes() > 0) {
310+
startField = MINUTE;
311+
if (period.getSeconds() > 0 || period.getMillis() > 0) {
312+
endField = Optional.of(SECOND);
313+
value = INTERVAL_MINUTE_SECOND_FORMATTER.print(period);
314+
}
315+
else {
316+
endField = Optional.empty();
317+
value = INTERVAL_MINUTE_FORMATTER.print(period);
318+
}
319+
}
320+
else {
321+
startField = SECOND;
322+
endField = Optional.empty();
323+
value = INTERVAL_SECOND_FORMATTER.print(period);
324+
}
325+
326+
return new IntervalLiteral(value, millis < 0 ? NEGATIVE : POSITIVE, startField, endField);
327+
}
328+
257329
private static long parsePeriodMillis(PeriodFormatter periodFormatter, String value)
258330
{
259331
Period period = parsePeriod(periodFormatter, value);
@@ -337,7 +409,8 @@ private static PeriodFormatter createPeriodFormatter(IntervalField startField, I
337409

338410
List<PeriodParser> parsers = new ArrayList<>();
339411

340-
PeriodFormatterBuilder builder = new PeriodFormatterBuilder();
412+
PeriodFormatterBuilder builder = new PeriodFormatterBuilder()
413+
.printZeroIfSupported();
341414
switch (startField) {
342415
case YEAR:
343416
builder.appendYears();
@@ -372,6 +445,7 @@ private static PeriodFormatter createPeriodFormatter(IntervalField startField, I
372445
break;
373446
}
374447
builder.appendLiteral(":");
448+
builder.minimumPrintedDigits(2);
375449
// fall through
376450

377451
case MINUTE:
@@ -381,6 +455,7 @@ private static PeriodFormatter createPeriodFormatter(IntervalField startField, I
381455
break;
382456
}
383457
builder.appendLiteral(":");
458+
builder.minimumPrintedDigits(2);
384459
// fall through
385460

386461
case SECOND:

core/trino-main/src/test/java/io/trino/util/TestDateTimeUtils.java

Lines changed: 89 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,10 +13,22 @@
1313
*/
1414
package io.trino.util;
1515

16+
import io.trino.sql.tree.IntervalLiteral;
17+
import io.trino.sql.tree.IntervalLiteral.IntervalField;
1618
import org.junit.jupiter.api.Test;
1719

1820
import java.time.DateTimeException;
21+
import java.time.Duration;
22+
import java.util.Optional;
1923

24+
import static io.trino.sql.tree.IntervalLiteral.IntervalField.DAY;
25+
import static io.trino.sql.tree.IntervalLiteral.IntervalField.HOUR;
26+
import static io.trino.sql.tree.IntervalLiteral.IntervalField.MINUTE;
27+
import static io.trino.sql.tree.IntervalLiteral.IntervalField.SECOND;
28+
import static io.trino.sql.tree.IntervalLiteral.Sign.NEGATIVE;
29+
import static io.trino.sql.tree.IntervalLiteral.Sign.POSITIVE;
30+
import static io.trino.util.DateTimeUtils.formatDayTimeInterval;
31+
import static io.trino.util.DateTimeUtils.parseDayTimeInterval;
2032
import static io.trino.util.DateTimeUtils.parseIfIso8601DateFormat;
2133
import static org.assertj.core.api.Assertions.assertThat;
2234
import static org.assertj.core.api.Assertions.assertThatThrownBy;
@@ -85,4 +97,81 @@ public void testParseIfIso8601DateFormat()
8597
.isInstanceOf(DateTimeException.class)
8698
.hasMessage("Invalid value for DayOfMonth (valid values 1 - 28/31): 41");
8799
}
100+
101+
@Test
102+
void testDayTimeIntervalRoundTrip()
103+
{
104+
testDayTimeIntervalRoundTrip("0", SECOND, Optional.empty());
105+
testDayTimeIntervalRoundTrip("0.000", SECOND, Optional.empty(), "0", SECOND, Optional.empty());
106+
testDayTimeIntervalRoundTrip("45", SECOND, Optional.empty());
107+
testDayTimeIntervalRoundTrip("0.555", SECOND, Optional.empty());
108+
testDayTimeIntervalRoundTrip("59.999", SECOND, Optional.empty());
109+
testDayTimeIntervalRoundTrip("60", SECOND, Optional.empty(), "1", MINUTE, Optional.empty());
110+
testDayTimeIntervalRoundTrip("61", SECOND, Optional.empty(), "1:01", MINUTE, Optional.of(SECOND));
111+
testDayTimeIntervalRoundTrip("3661", SECOND, Optional.empty(), "1:01:01", HOUR, Optional.of(SECOND));
112+
testDayTimeIntervalRoundTrip("90061", SECOND, Optional.empty(), "1 1:01:01", DAY, Optional.of(SECOND));
113+
114+
testDayTimeIntervalRoundTrip("0", MINUTE, Optional.empty(), "0", SECOND, Optional.empty());
115+
testDayTimeIntervalRoundTrip("25", MINUTE, Optional.empty());
116+
testDayTimeIntervalRoundTrip("15:30", MINUTE, Optional.of(SECOND));
117+
testDayTimeIntervalRoundTrip("59:00.999", MINUTE, Optional.of(SECOND));
118+
testDayTimeIntervalRoundTrip("60", MINUTE, Optional.empty(), "1", HOUR, Optional.empty());
119+
testDayTimeIntervalRoundTrip("61", MINUTE, Optional.empty(), "1:01", HOUR, Optional.of(MINUTE));
120+
testDayTimeIntervalRoundTrip("1500", MINUTE, Optional.empty(), "1 1", DAY, Optional.of(HOUR));
121+
testDayTimeIntervalRoundTrip("1501", MINUTE, Optional.empty(), "1 1:01", DAY, Optional.of(MINUTE));
122+
123+
testDayTimeIntervalRoundTrip("0", HOUR, Optional.empty(), "0", SECOND, Optional.empty());
124+
testDayTimeIntervalRoundTrip("8", HOUR, Optional.empty());
125+
testDayTimeIntervalRoundTrip("2:45", HOUR, Optional.of(MINUTE));
126+
testDayTimeIntervalRoundTrip("2:00:45", HOUR, Optional.of(SECOND));
127+
testDayTimeIntervalRoundTrip("1:30:45", HOUR, Optional.of(SECOND));
128+
testDayTimeIntervalRoundTrip("1:00:00.999", HOUR, Optional.of(SECOND));
129+
testDayTimeIntervalRoundTrip("24", HOUR, Optional.empty(), "1", DAY, Optional.empty());
130+
testDayTimeIntervalRoundTrip("25", HOUR, Optional.empty(), "1 1", DAY, Optional.of(HOUR));
131+
testDayTimeIntervalRoundTrip("17520", HOUR, Optional.empty(), "730", DAY, Optional.empty());
132+
133+
testDayTimeIntervalRoundTrip("0", DAY, Optional.empty(), "0", SECOND, Optional.empty());
134+
testDayTimeIntervalRoundTrip("340", DAY, Optional.empty());
135+
testDayTimeIntervalRoundTrip("2 6", DAY, Optional.of(HOUR));
136+
testDayTimeIntervalRoundTrip("3 0:30", DAY, Optional.of(MINUTE));
137+
testDayTimeIntervalRoundTrip("3 12:30", DAY, Optional.of(MINUTE));
138+
testDayTimeIntervalRoundTrip("1 0:00:15", DAY, Optional.of(SECOND));
139+
testDayTimeIntervalRoundTrip("1 4:20:15", DAY, Optional.of(SECOND));
140+
testDayTimeIntervalRoundTrip("1 0:00:00.999", DAY, Optional.of(SECOND));
141+
testDayTimeIntervalRoundTrip("1 23:59:59.999", DAY, Optional.of(SECOND));
142+
}
143+
144+
private static void testDayTimeIntervalRoundTrip(String value, IntervalField start, Optional<IntervalField> end)
145+
{
146+
testDayTimeIntervalRoundTrip(value, start, end, value, start, end);
147+
}
148+
149+
private static void testDayTimeIntervalRoundTrip(
150+
String inputValue,
151+
IntervalField inputStart,
152+
Optional<IntervalField> inputEnd,
153+
String expectedValue,
154+
IntervalField expectedStart,
155+
Optional<IntervalField> expectedEnd)
156+
{
157+
long millis = parseDayTimeInterval(inputValue, inputStart, inputEnd);
158+
assertThat(millis).isGreaterThanOrEqualTo(0);
159+
160+
IntervalLiteral positiveInterval = formatDayTimeInterval(Duration.ofMillis(millis));
161+
assertThat(positiveInterval.getSign()).isEqualTo(POSITIVE);
162+
assertThat(positiveInterval.getStartField()).isEqualTo(expectedStart);
163+
assertThat(positiveInterval.getEndField()).isEqualTo(expectedEnd);
164+
assertThat(positiveInterval.getValue()).isEqualTo(expectedValue);
165+
166+
if (millis != 0) {
167+
long millisNegative = parseDayTimeInterval("-" + inputValue, inputStart, inputEnd);
168+
assertThat(millisNegative).isLessThan(0);
169+
170+
IntervalLiteral negativeInterval = formatDayTimeInterval(Duration.ofMillis(millisNegative));
171+
assertThat(negativeInterval.getSign()).isEqualTo(NEGATIVE);
172+
assertThat(negativeInterval.getStartField()).isEqualTo(expectedStart);
173+
assertThat(negativeInterval.getEndField()).isEqualTo(expectedEnd);
174+
assertThat(negativeInterval.getValue()).isEqualTo(expectedValue);
175+
}
176+
}
88177
}

testing/trino-testing/src/main/java/io/trino/testing/BaseConnectorTest.java

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1531,6 +1531,22 @@ public void testMaterializedViewGracePeriod()
15311531
assertUpdate("CREATE MATERIALIZED VIEW " + viewName + " " +
15321532
"GRACE PERIOD INTERVAL '1' HOUR " +
15331533
"AS SELECT DISTINCT regionkey, format('%s', name) name FROM " + table.getName());
1534+
assertThat((String) computeScalar("SHOW CREATE MATERIALIZED VIEW " + viewName))
1535+
.matches(
1536+
"""
1537+
CREATE MATERIALIZED VIEW iceberg\\.tpch\\.%s
1538+
GRACE PERIOD INTERVAL '1' HOUR
1539+
WITH \\(
1540+
format = 'PARQUET',
1541+
format_version = 2,
1542+
location = 'local:///iceberg/tpch/test_mv_grace_period.*',
1543+
storage_schema = 'tpch'
1544+
\\) AS
1545+
SELECT DISTINCT
1546+
regionkey
1547+
, format\\('%%s', name\\) name
1548+
FROM
1549+
%s""".formatted(viewName, table.getName()));
15341550

15351551
String initialResults = "SELECT DISTINCT regionkey, CAST(name AS varchar) FROM region";
15361552

0 commit comments

Comments
 (0)