Skip to content

Commit 2c69b65

Browse files
committed
Add GRACE PERIOD to SHOW CREATE MATERIALIZED VIEW
1 parent 81f8713 commit 2c69b65

File tree

4 files changed

+120
-2
lines changed

4 files changed

+120
-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
Optional.empty(), // TODO support WHEN STALE
566568
propertyNodes,
567569
viewDefinition.get().getComment())).trim();

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

Lines changed: 38 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,10 +18,12 @@
1818
import io.trino.client.IntervalYearMonth;
1919
import io.trino.spi.TrinoException;
2020
import io.trino.spi.type.TimeZoneKey;
21+
import io.trino.sql.tree.IntervalLiteral;
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,10 @@
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.SECOND;
54+
import static io.trino.sql.tree.IntervalLiteral.Sign.NEGATIVE;
55+
import static io.trino.sql.tree.IntervalLiteral.Sign.POSITIVE;
4956
import static io.trino.util.DateTimeZoneIndex.getChronology;
5057
import static io.trino.util.DateTimeZoneIndex.packDateTimeWithZone;
5158
import static java.lang.Math.toIntExact;
@@ -254,6 +261,19 @@ public static long parseDayTimeInterval(String value, IntervalField startField,
254261
throw invalidQualifier(startField, endField.orElse(startField));
255262
}
256263

264+
public static IntervalLiteral formatDayTimeInterval(Duration duration)
265+
{
266+
long millis = duration.toMillis();
267+
IntervalLiteral.Sign sign = millis < 0 ? NEGATIVE : POSITIVE;
268+
Period period = new Period(Math.abs(millis)).normalizedStandard(PeriodType.dayTime());
269+
// Always use INTERVAL DAY TO SECOND. The output is more verbose
270+
// (e.g., "1 0:00:00" instead of "1"), but this avoids the need to
271+
// determine the minimal field range and choose a specialized formatter.
272+
String value = INTERVAL_DAY_SECOND_FORMATTER.print(period);
273+
274+
return new IntervalLiteral(value, sign, DAY, Optional.of(SECOND));
275+
}
276+
257277
private static long parsePeriodMillis(PeriodFormatter periodFormatter, String value)
258278
{
259279
Period period = parsePeriod(periodFormatter, value);
@@ -337,7 +357,12 @@ private static PeriodFormatter createPeriodFormatter(IntervalField startField, I
337357

338358
List<PeriodParser> parsers = new ArrayList<>();
339359

340-
PeriodFormatterBuilder builder = new PeriodFormatterBuilder();
360+
PeriodFormatterBuilder builder = new PeriodFormatterBuilder()
361+
// Ensures zero-valued fields are printed instead of omitted. This affects printing only, not parsing.
362+
// Example for INTERVAL HOUR TO SECOND:
363+
// With printZeroIfSupported(): "2:00:45"
364+
// Without printZeroIfSupported(): "2::45"
365+
.printZeroIfSupported();
341366
switch (startField) {
342367
case YEAR:
343368
builder.appendYears();
@@ -372,6 +397,12 @@ private static PeriodFormatter createPeriodFormatter(IntervalField startField, I
372397
break;
373398
}
374399
builder.appendLiteral(":");
400+
// Ensures fixed-width, zero-padded minutes. This affects printing only, not parsing.
401+
// Applies to the next appended field (minutes).
402+
// Example for INTERVAL HOUR TO MINUTE:
403+
// With minimumPrintedDigits(2): "2:05"
404+
// Without minimumPrintedDigits(2): "2:5"
405+
builder.minimumPrintedDigits(2);
375406
// fall through
376407

377408
case MINUTE:
@@ -381,6 +412,12 @@ private static PeriodFormatter createPeriodFormatter(IntervalField startField, I
381412
break;
382413
}
383414
builder.appendLiteral(":");
415+
// Ensures fixed-width, zero-padded seconds. This affects printing only, not parsing.
416+
// Applies to the next appended field (seconds).
417+
// Example for INTERVAL HOUR TO SECOND:
418+
// With minimumPrintedDigits(2): "2:05:07"
419+
// Without minimumPrintedDigits(2): "2:05:7"
420+
builder.minimumPrintedDigits(2);
384421
// fall through
385422

386423
case SECOND:

core/trino-main/src/test/java/io/trino/type/TestIntervalDayTime.java

Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,13 +13,19 @@
1313
*/
1414
package io.trino.type;
1515

16+
import io.trino.sql.ExpressionFormatter;
1617
import io.trino.sql.query.QueryAssertions;
18+
import io.trino.sql.query.QueryAssertions.ExpressionAssertProvider.Result;
19+
import io.trino.sql.tree.IntervalLiteral;
20+
import org.intellij.lang.annotations.Language;
1721
import org.junit.jupiter.api.AfterAll;
1822
import org.junit.jupiter.api.BeforeAll;
1923
import org.junit.jupiter.api.Test;
2024
import org.junit.jupiter.api.TestInstance;
2125
import org.junit.jupiter.api.parallel.Execution;
2226

27+
import java.time.Duration;
28+
2329
import static io.trino.spi.StandardErrorCode.INVALID_FUNCTION_ARGUMENT;
2430
import static io.trino.spi.function.OperatorType.ADD;
2531
import static io.trino.spi.function.OperatorType.DIVIDE;
@@ -32,6 +38,7 @@
3238
import static io.trino.spi.function.OperatorType.SUBTRACT;
3339
import static io.trino.spi.type.VarcharType.VARCHAR;
3440
import static io.trino.testing.assertions.TrinoExceptionAssert.assertTrinoExceptionThrownBy;
41+
import static io.trino.util.DateTimeUtils.formatDayTimeInterval;
3542
import static java.util.concurrent.TimeUnit.DAYS;
3643
import static org.assertj.core.api.Assertions.assertThat;
3744
import static org.junit.jupiter.api.TestInstance.Lifecycle.PER_CLASS;
@@ -523,4 +530,74 @@ public void testIndeterminate()
523530
assertThat(assertions.operator(INDETERMINATE, "INTERVAL '45' MINUTE TO SECOND"))
524531
.isEqualTo(false);
525532
}
533+
534+
@Test
535+
void testIntervalDayTimeRoundTrip()
536+
{
537+
testIntervalDayTimeRoundTrip("INTERVAL '0' SECOND", "INTERVAL '0 0:00:00' DAY TO SECOND");
538+
testIntervalDayTimeRoundTrip("INTERVAL -'0' SECOND", "INTERVAL '0 0:00:00' DAY TO SECOND");
539+
testIntervalDayTimeRoundTrip("INTERVAL '0.000' SECOND", "INTERVAL '0 0:00:00' DAY TO SECOND");
540+
testIntervalDayTimeRoundTrip("INTERVAL '0.4' SECOND", "INTERVAL '0 0:00:00.400' DAY TO SECOND");
541+
testIntervalDayTimeRoundTrip("INTERVAL '0.04' SECOND", "INTERVAL '0 0:00:00.040' DAY TO SECOND");
542+
testIntervalDayTimeRoundTrip("INTERVAL '0.040' SECOND", "INTERVAL '0 0:00:00.040' DAY TO SECOND");
543+
testIntervalDayTimeRoundTrip("INTERVAL '45' SECOND", "INTERVAL '0 0:00:45' DAY TO SECOND");
544+
testIntervalDayTimeRoundTrip("INTERVAL -'45' SECOND", "INTERVAL -'0 0:00:45' DAY TO SECOND");
545+
testIntervalDayTimeRoundTrip("INTERVAL '0.555' SECOND", "INTERVAL '0 0:00:00.555' DAY TO SECOND");
546+
testIntervalDayTimeRoundTrip("INTERVAL '59.999' SECOND", "INTERVAL '0 0:00:59.999' DAY TO SECOND");
547+
testIntervalDayTimeRoundTrip("INTERVAL '60' SECOND", "INTERVAL '0 0:01:00' DAY TO SECOND");
548+
testIntervalDayTimeRoundTrip("INTERVAL '61' SECOND", "INTERVAL '0 0:01:01' DAY TO SECOND");
549+
testIntervalDayTimeRoundTrip("INTERVAL '3661' SECOND", "INTERVAL '0 1:01:01' DAY TO SECOND");
550+
testIntervalDayTimeRoundTrip("INTERVAL '90061' SECOND", "INTERVAL '1 1:01:01' DAY TO SECOND");
551+
552+
testIntervalDayTimeRoundTrip("INTERVAL '0' MINUTE", "INTERVAL '0 0:00:00' DAY TO SECOND");
553+
testIntervalDayTimeRoundTrip("INTERVAL -'0' MINUTE", "INTERVAL '0 0:00:00' DAY TO SECOND");
554+
testIntervalDayTimeRoundTrip("INTERVAL '25' MINUTE", "INTERVAL '0 0:25:00' DAY TO SECOND");
555+
testIntervalDayTimeRoundTrip("INTERVAL -'25' MINUTE", "INTERVAL -'0 0:25:00' DAY TO SECOND");
556+
testIntervalDayTimeRoundTrip("INTERVAL '15:30' MINUTE TO SECOND", "INTERVAL '0 0:15:30' DAY TO SECOND");
557+
testIntervalDayTimeRoundTrip("INTERVAL '59:00.999' MINUTE TO SECOND", "INTERVAL '0 0:59:00.999' DAY TO SECOND");
558+
testIntervalDayTimeRoundTrip("INTERVAL '60' MINUTE", "INTERVAL '0 1:00:00' DAY TO SECOND");
559+
testIntervalDayTimeRoundTrip("INTERVAL '61' MINUTE", "INTERVAL '0 1:01:00' DAY TO SECOND");
560+
testIntervalDayTimeRoundTrip("INTERVAL '1500' MINUTE", "INTERVAL '1 1:00:00' DAY TO SECOND");
561+
testIntervalDayTimeRoundTrip("INTERVAL '1501' MINUTE", "INTERVAL '1 1:01:00' DAY TO SECOND");
562+
563+
testIntervalDayTimeRoundTrip("INTERVAL '0' HOUR", "INTERVAL '0 0:00:00' DAY TO SECOND");
564+
testIntervalDayTimeRoundTrip("INTERVAL -'0' HOUR", "INTERVAL '0 0:00:00' DAY TO SECOND");
565+
testIntervalDayTimeRoundTrip("INTERVAL '8' HOUR", "INTERVAL '0 8:00:00' DAY TO SECOND");
566+
testIntervalDayTimeRoundTrip("INTERVAL -'8' HOUR", "INTERVAL -'0 8:00:00' DAY TO SECOND");
567+
testIntervalDayTimeRoundTrip("INTERVAL '2:45' HOUR TO MINUTE", "INTERVAL '0 2:45:00' DAY TO SECOND");
568+
testIntervalDayTimeRoundTrip("INTERVAL '2:00:45' HOUR TO SECOND", "INTERVAL '0 2:00:45' DAY TO SECOND");
569+
testIntervalDayTimeRoundTrip("INTERVAL '1:30:45' HOUR TO SECOND", "INTERVAL '0 1:30:45' DAY TO SECOND");
570+
testIntervalDayTimeRoundTrip("INTERVAL '1:00:00.999' HOUR TO SECOND", "INTERVAL '0 1:00:00.999' DAY TO SECOND");
571+
testIntervalDayTimeRoundTrip("INTERVAL '24' HOUR", "INTERVAL '1 0:00:00' DAY TO SECOND");
572+
testIntervalDayTimeRoundTrip("INTERVAL '25' HOUR", "INTERVAL '1 1:00:00' DAY TO SECOND");
573+
testIntervalDayTimeRoundTrip("INTERVAL '17520' HOUR", "INTERVAL '730 0:00:00' DAY TO SECOND");
574+
575+
testIntervalDayTimeRoundTrip("INTERVAL '0' DAY", "INTERVAL '0 0:00:00' DAY TO SECOND");
576+
testIntervalDayTimeRoundTrip("INTERVAL -'0' DAY", "INTERVAL '0 0:00:00' DAY TO SECOND");
577+
testIntervalDayTimeRoundTrip("INTERVAL '340' DAY", "INTERVAL '340 0:00:00' DAY TO SECOND");
578+
testIntervalDayTimeRoundTrip("INTERVAL -'340' DAY", "INTERVAL -'340 0:00:00' DAY TO SECOND");
579+
testIntervalDayTimeRoundTrip("INTERVAL '2 6' DAY TO HOUR", "INTERVAL '2 6:00:00' DAY TO SECOND");
580+
testIntervalDayTimeRoundTrip("INTERVAL '3 0:30' DAY TO MINUTE", "INTERVAL '3 0:30:00' DAY TO SECOND");
581+
testIntervalDayTimeRoundTrip("INTERVAL '3 12:30' DAY TO MINUTE", "INTERVAL '3 12:30:00' DAY TO SECOND");
582+
testIntervalDayTimeRoundTrip("INTERVAL '1 0:00:15' DAY TO SECOND", "INTERVAL '1 0:00:15' DAY TO SECOND");
583+
testIntervalDayTimeRoundTrip("INTERVAL '1 4:20:15' DAY TO SECOND", "INTERVAL '1 4:20:15' DAY TO SECOND");
584+
testIntervalDayTimeRoundTrip("INTERVAL '1 0:00:00.999' DAY TO SECOND", "INTERVAL '1 0:00:00.999' DAY TO SECOND");
585+
testIntervalDayTimeRoundTrip("INTERVAL '1 23:59:59.999' DAY TO SECOND", "INTERVAL '1 23:59:59.999' DAY TO SECOND");
586+
}
587+
588+
private void testIntervalDayTimeRoundTrip(@Language("SQL") String input, @Language("SQL") String expectedFormatted)
589+
{
590+
Result evaluatedResult = assertions.expression(input).evaluate();
591+
assertThat(evaluatedResult.type()).isEqualTo(IntervalDayTimeType.INTERVAL_DAY_TIME);
592+
SqlIntervalDayTime originalInterval = (SqlIntervalDayTime) evaluatedResult.value();
593+
594+
Duration duration = Duration.ofMillis(originalInterval.getMillis());
595+
IntervalLiteral formattedLiteral = formatDayTimeInterval(duration);
596+
String formatted = ExpressionFormatter.formatExpression(formattedLiteral);
597+
assertThat(formatted).isEqualTo(expectedFormatted);
598+
599+
Result reparsedResult = assertions.expression(formatted).evaluate();
600+
SqlIntervalDayTime reparsedInterval = (SqlIntervalDayTime) reparsedResult.value();
601+
assertThat(reparsedInterval).isEqualTo(originalInterval);
602+
}
526603
}

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

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1532,6 +1532,8 @@ public void testMaterializedViewGracePeriod()
15321532
assertUpdate("CREATE MATERIALIZED VIEW " + viewName + " " +
15331533
"GRACE PERIOD INTERVAL '1' HOUR " +
15341534
"AS SELECT DISTINCT regionkey, format('%s', name) name FROM " + table.getName());
1535+
assertThat((String) computeScalar("SHOW CREATE MATERIALIZED VIEW " + viewName))
1536+
.matches("(?sm).*^GRACE PERIOD INTERVAL '0 1:00:00' DAY TO SECOND$.*");
15351537

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

0 commit comments

Comments
 (0)