Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions core/src/main/java/org/apache/calcite/rex/RexBuilder.java
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,7 @@
import org.apache.calcite.util.DateString;
import org.apache.calcite.util.NlsString;
import org.apache.calcite.util.Sarg;
import org.apache.calcite.util.SqlValueComparator;
import org.apache.calcite.util.TimeString;
import org.apache.calcite.util.TimeWithTimeZoneString;
import org.apache.calcite.util.TimestampString;
Expand Down Expand Up @@ -1996,6 +1997,8 @@ public RexNode makeBetween(RexNode arg, RexNode lower, RexNode upper) {
final Comparable upperValue = toComparable(Comparable.class, upper);
if (lowerValue != null
&& upperValue != null
&& !SqlValueComparator.INSTANCE.hasCustomSqlValueComparison(lowerValue)
&& !SqlValueComparator.INSTANCE.hasCustomSqlValueComparison(upperValue)
&& areAssignable(arg, Arrays.asList(lower, upper))) {
final Sarg sarg =
Sarg.of(RexUnknownAs.UNKNOWN,
Expand Down Expand Up @@ -2028,6 +2031,9 @@ && areAssignable(arg, Arrays.asList(lower, upper))) {
if (value == null) {
return null;
}
if (SqlValueComparator.INSTANCE.hasCustomSqlValueComparison(value)) {
return null;
}
rangeSet.add(Range.singleton(value));
}
return Sarg.of(unknownAs, rangeSet);
Expand Down
4 changes: 2 additions & 2 deletions core/src/main/java/org/apache/calcite/rex/RexInterpreter.java
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@
import org.apache.calcite.util.NlsString;
import org.apache.calcite.util.RangeSets;
import org.apache.calcite.util.Sarg;
import org.apache.calcite.util.SqlValueComparator;
import org.apache.calcite.util.TimeString;
import org.apache.calcite.util.TimestampString;
import org.apache.calcite.util.Util;
Expand Down Expand Up @@ -481,8 +482,7 @@ private static Comparable compare(List<Comparable> values, IntPredicate p) {
if (v1 instanceof Number) {
v1 = number(v1);
}
//noinspection unchecked
final int c = v0.compareTo(v1);
final int c = SqlValueComparator.INSTANCE.compare(v0, v1);
return p.test(c);
}

Expand Down
17 changes: 1 addition & 16 deletions core/src/main/java/org/apache/calcite/rex/RexLiteral.java
Original file line number Diff line number Diff line change
Expand Up @@ -60,11 +60,6 @@
import java.nio.ByteBuffer;
import java.nio.charset.Charset;
import java.text.SimpleDateFormat;
import java.time.Instant;
import java.time.LocalDateTime;
import java.time.ZoneId;
import java.time.ZoneOffset;
import java.time.ZonedDateTime;
import java.util.Calendar;
import java.util.List;
import java.util.Locale;
Expand Down Expand Up @@ -1189,17 +1184,7 @@ public boolean isNull() {
case TIMESTAMP_TZ:
if (clazz == Long.class) {
TimestampWithTimeZoneString tstz = (TimestampWithTimeZoneString) value;
long ms = tstz
.getLocalTimestampString()
.getMillisSinceEpoch();
// Interpret the timestamp part as a UTC timestamp
LocalDateTime local = Instant.ofEpochMilli(ms).atZone(ZoneOffset.UTC).toLocalDateTime();
// Adjust for the time zone
ZoneId id = tstz.getTimeZone().toZoneId();
ZonedDateTime zoned = local.atZone(id);
ZonedDateTime utc = zoned.withZoneSameInstant(ZoneOffset.UTC);
ms = utc.toInstant().toEpochMilli();
return clazz.cast(ms);
return clazz.cast(tstz.getMillisSinceEpoch());
} else if (clazz == Calendar.class) {
TimestampWithTimeZoneString ts = (TimestampWithTimeZoneString) value;
return clazz.cast(ts.getLocalTimestampString().toCalendar(ts.getTimeZone()));
Expand Down
38 changes: 28 additions & 10 deletions core/src/main/java/org/apache/calcite/rex/RexSimplify.java
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@
import org.apache.calcite.util.Pair;
import org.apache.calcite.util.RangeSets;
import org.apache.calcite.util.Sarg;
import org.apache.calcite.util.SqlValueComparator;
import org.apache.calcite.util.Util;

import com.google.common.collect.ArrayListMultimap;
Expand Down Expand Up @@ -747,7 +748,7 @@ private <C extends Comparable<C>> RexNode simplifyComparison(RexCall e,
? rexBuilder.makeLiteral(false)
: rexBuilder.makeNullLiteral(e.getType());
}
final int comparisonResult = v0.compareTo(v1);
final int comparisonResult = SqlValueComparator.INSTANCE.compare(v0, v1);
switch (e.getKind()) {
case EQUALS:
return rexBuilder.makeLiteral(comparisonResult == 0);
Expand Down Expand Up @@ -1866,7 +1867,8 @@ private <C extends Comparable<C>> RexNode simplifyAnd2ForUnknownAsFalse(
if (comparison != null
&& comparison.kind != SqlKind.NOT_EQUALS) { // not supported yet
final C v0 = comparison.literal.getValueAs(clazz);
if (v0 != null) {
if (v0 != null
&& !SqlValueComparator.INSTANCE.hasCustomSqlValueComparison(v0)) {
final RexNode result =
processRange(rexBuilder, terms, rangeTerms,
predicate, comparison.ref, v0, comparison.kind);
Expand Down Expand Up @@ -1961,12 +1963,14 @@ private <C extends Comparable<C>> RexNode simplifyAnd2ForUnknownAsFalse(
if (constant == null) {
break;
}
final RexNode result =
processRange(rexBuilder, terms, rangeTerms,
term, comparison.ref, constant, comparison.kind);
if (result != null) {
// Not satisfiable
return result;
if (!SqlValueComparator.INSTANCE.hasCustomSqlValueComparison(constant)) {
final RexNode result =
processRange(rexBuilder, terms, rangeTerms,
term, comparison.ref, constant, comparison.kind);
if (result != null) {
// Not satisfiable
return result;
}
}
}
break;
Expand Down Expand Up @@ -2091,6 +2095,9 @@ private <C extends Comparable<C>> RexNode simplifyUsingPredicates(RexNode e,
if (v0 == null) {
return e;
}
if (SqlValueComparator.INSTANCE.hasCustomSqlValueComparison(v0)) {
return e;
}
final RangeSet<C> rangeSet = rangeSet(comparison.kind, v0);
final RangeSet<C> rangeSet2 =
residue(comparison.ref, rangeSet, predicates.pulledUpPredicates,
Expand Down Expand Up @@ -2283,10 +2290,9 @@ private RexNode simplifyOrs(List<RexNode> terms, RexUnknownAs unknownAs) {
final Comparable comparable1 = notEqualsComparison.literal.getValue();
final Comparable comparable2 =
castNonNull(Comparison.of(prevNotEquals)).literal.getValue();
//noinspection unchecked
if (comparable1 != null
&& comparable2 != null
&& comparable1.compareTo(comparable2) != 0) {
&& SqlValueComparator.INSTANCE.compare(comparable1, comparable2) != 0) {
// X <> A OR X <> B => X IS NOT NULL OR NULL
final RexNode isNotNull =
rexBuilder.makeCall(RexUtil.getPos(term), SqlStdOperatorTable.IS_NOT_NULL,
Expand Down Expand Up @@ -3073,6 +3079,15 @@ private static boolean constantsEquivalent(RexNode node1, RexNode node2) {
if (Objects.equals(node1, node2)) {
return true;
}
if (node1 instanceof RexLiteral && node2 instanceof RexLiteral
&& SqlTypeUtil.equalSansNullability(node1.getType(), node2.getType())) {
final Comparable value1 = ((RexLiteral) node1).getValueAs(Comparable.class);
final Comparable value2 = ((RexLiteral) node2).getValueAs(Comparable.class);
return value1 != null
&& value2 != null
&& SqlValueComparator.INSTANCE.hasCustomSqlValueComparison(value1, value2)
&& SqlValueComparator.INSTANCE.compare(value1, value2) == 0;
}
if (!(node1 instanceof RexCall) || !(node2 instanceof RexCall)) {
return false;
}
Expand Down Expand Up @@ -3437,6 +3452,9 @@ private boolean accept2b(SqlParserPos pos, RexNode e, SqlKind kind,
}
final Comparable value =
requireNonNull(literal.getValueAs(Comparable.class), "value");
if (SqlValueComparator.INSTANCE.hasCustomSqlValueComparison(value)) {
return false;
}
switch (kind) {
case LESS_THAN:
b.addRange(Range.lessThan(value), literal.getType());
Expand Down
27 changes: 27 additions & 0 deletions core/src/main/java/org/apache/calcite/util/ComparableSqlValue.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
/*
* 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.calcite.util;

/**
* Value whose SQL comparison semantics differ from its natural ordering.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This seems to be defined only for constant values (a concept which includes literals, but also perhaps constructors applied to constant arguments). The term "value" you used is very overloaded - it can be applied to runtime values as well.

*
* @param <T> Type of value to compare against
*/
public interface ComparableSqlValue<T> {
/** Compares this value to another value using SQL value semantics. */
int compareSqlValueTo(T o);
}
47 changes: 47 additions & 0 deletions core/src/main/java/org/apache/calcite/util/SqlValueComparator.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
/*
* 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.calcite.util;

import java.util.Comparator;

/** Compares values using SQL value semantics. */
public final class SqlValueComparator implements Comparator<Comparable> {
/** Singleton instance. */
public static final SqlValueComparator INSTANCE = new SqlValueComparator();

private SqlValueComparator() {
}

@SuppressWarnings({"rawtypes", "unchecked"})
@Override public int compare(Comparable v0, Comparable v1) {
if (hasCustomSqlValueComparison(v0, v1)) {
return ((ComparableSqlValue) v0).compareSqlValueTo(v1);
}
return v0.compareTo(v1);
}

/** Returns whether two values should use custom SQL-value comparison. */
public boolean hasCustomSqlValueComparison(Comparable v0, Comparable v1) {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is the two-argument version of this function really necessary?
Is there a case where the one-argument and the two-argument would return different results for the same first argument?

return v0 instanceof ComparableSqlValue
&& v0.getClass() == v1.getClass();
}

/** Returns whether a value should use custom SQL-value comparison. */
public boolean hasCustomSqlValueComparison(Comparable v) {
return v instanceof ComparableSqlValue;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,8 @@
* and can support unlimited precision (milliseconds, nanoseconds).
*/
public class TimestampWithTimeZoneString
implements Comparable<TimestampWithTimeZoneString> {
implements Comparable<TimestampWithTimeZoneString>,
ComparableSqlValue<TimestampWithTimeZoneString> {

final TimestampString localDateTime;
final TimeZone timeZone;
Expand Down Expand Up @@ -173,7 +174,22 @@ public TimestampWithTimeZoneString withTimeZone(TimeZone timeZone) {
}

@Override public int compareTo(TimestampWithTimeZoneString o) {
return this.pt.getCalendar().compareTo(o.pt.getCalendar());
final int c = compareToInstant(o);
return c != 0 ? c : v.compareTo(o.v);
}

/** Compares this timestamp to another timestamp by instant. */
public int compareToInstant(TimestampWithTimeZoneString o) {
return Long.compare(getMillisSinceEpoch(), o.getMillisSinceEpoch());
}

@Override public int compareSqlValueTo(TimestampWithTimeZoneString o) {
return compareToInstant(o);
}

/** Returns the number of milliseconds since the epoch. */
public long getMillisSinceEpoch() {
return pt.getCalendar().getTimeInMillis();
}

public TimestampWithTimeZoneString round(int precision) {
Expand Down
42 changes: 42 additions & 0 deletions core/src/test/java/org/apache/calcite/rex/RexBuilderTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -964,6 +964,48 @@ private void checkDate(RexLiteral literal) {
assertThat(inCall.getKind(), is(SqlKind.SEARCH));
}

/** Test case for <a href="https://issues.apache.org/jira/browse/CALCITE-7553">[CALCITE-7553]
* TIMESTAMP_TZ IN-list construction should not use Sarg ordering</a>. */
@Test void testMakeInTimestampWithTimeZoneDoesNotCreateSarg() {
final RelDataTypeFactory typeFactory =
new SqlTypeFactoryImpl(RelDataTypeSystem.DEFAULT);
final RexBuilder rexBuilder = new RexBuilder(typeFactory);
final RelDataType timestampTzType =
typeFactory.createSqlType(SqlTypeName.TIMESTAMP_TZ, 0);
final RexNode input = rexBuilder.makeInputRef(timestampTzType, 0);
final RexLiteral pst =
rexBuilder.makeTimestampTzLiteral(
new TimestampWithTimeZoneString("1969-07-21 02:56:15 GMT-08:00"), 0);
final RexLiteral gmt =
rexBuilder.makeTimestampTzLiteral(
new TimestampWithTimeZoneString("1969-07-21 10:56:15 GMT"), 0);

final RexNode inCall = rexBuilder.makeIn(input, ImmutableList.of(pst, gmt));

assertThat(inCall.getKind(), is(SqlKind.OR));
}

/** Test case for <a href="https://issues.apache.org/jira/browse/CALCITE-7553">[CALCITE-7553]
* TIMESTAMP_TZ BETWEEN construction should not use Sarg ordering</a>. */
@Test void testMakeBetweenTimestampWithTimeZoneDoesNotCreateSarg() {
final RelDataTypeFactory typeFactory =
new SqlTypeFactoryImpl(RelDataTypeSystem.DEFAULT);
final RexBuilder rexBuilder = new RexBuilder(typeFactory);
final RelDataType timestampTzType =
typeFactory.createSqlType(SqlTypeName.TIMESTAMP_TZ, 0);
final RexNode input = rexBuilder.makeInputRef(timestampTzType, 0);
final RexLiteral gmt =
rexBuilder.makeTimestampTzLiteral(
new TimestampWithTimeZoneString("1969-07-21 10:56:15 GMT"), 0);
final RexLiteral pst =
rexBuilder.makeTimestampTzLiteral(
new TimestampWithTimeZoneString("1969-07-21 02:56:15 GMT-08:00"), 0);

final RexNode betweenCall = rexBuilder.makeBetween(input, gmt, pst);

assertThat(betweenCall.getKind(), is(SqlKind.AND));
}

/**
* Test case for
* <a href="https://issues.apache.org/jira/browse/CALCITE-6989">[CALCITE-6989]
Expand Down
57 changes: 57 additions & 0 deletions core/src/test/java/org/apache/calcite/rex/RexProgramTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -3413,6 +3413,63 @@ private SqlOperator getNoDeterministicOperator() {
assertThat(timestampLTZChar1.equals(timestampLTZChar4), is(true));
}

/** Test case for <a href="https://issues.apache.org/jira/browse/CALCITE-7553">[CALCITE-7553]
* TIMESTAMP_TZ natural ordering must not collapse distinct values</a>. */
@Test void testTimestampWithTimeZoneNaturalOrderingKeepsDistinctValues() {
final TimestampWithTimeZoneString pst =
new TimestampWithTimeZoneString("1969-07-21 02:56:15 GMT-08:00");
final TimestampWithTimeZoneString gmt =
new TimestampWithTimeZoneString("1969-07-21 10:56:15 GMT");

assertNotEquals(pst, gmt);

final TreeMap<TimestampWithTimeZoneString, String> values = new TreeMap<>();
values.put(pst, "pst");
values.put(gmt, "gmt");

assertThat(values.keySet(), hasSize(2));
}

/** Test case for <a href="https://issues.apache.org/jira/browse/CALCITE-7553">[CALCITE-7553]
* SQL comparison of TIMESTAMP_TZ literals uses instant semantics</a>. */
@Test void testTimestampWithTimeZoneSqlComparisonUsesInstant() {
final RexLiteral pst =
rexBuilder.makeTimestampTzLiteral(
new TimestampWithTimeZoneString("1969-07-21 02:56:15 GMT-08:00"), 0);
final RexLiteral gmt =
rexBuilder.makeTimestampTzLiteral(
new TimestampWithTimeZoneString("1969-07-21 10:56:15 GMT"), 0);

checkSimplify(eq(pst, gmt), "true");
checkSimplify(ne(pst, gmt), "false");
checkSimplify(le(pst, gmt), "true");
checkSimplify(ge(pst, gmt), "true");
checkSimplify(lt(pst, gmt), "false");
checkSimplify(gt(pst, gmt), "false");

assertThat(eval(eq(pst, gmt)), is((Comparable) true));
assertThat(eval(ne(pst, gmt)), is((Comparable) false));
}

/** Test case for <a href="https://issues.apache.org/jira/browse/CALCITE-7553">[CALCITE-7553]
* TIMESTAMP_TZ equivalent constants do not create contradictory filters</a>. */
@Test void testTimestampWithTimeZoneEquivalentConstantsDoNotContradict() {
final RelDataType timestampTzType =
typeFactory.createSqlType(SqlTypeName.TIMESTAMP_TZ, 0);
final RexNode input = input(timestampTzType, 0);
final RexLiteral pst =
rexBuilder.makeTimestampTzLiteral(
new TimestampWithTimeZoneString("1969-07-21 02:56:15 GMT-08:00"), 0);
final RexLiteral gmt =
rexBuilder.makeTimestampTzLiteral(
new TimestampWithTimeZoneString("1969-07-21 10:56:15 GMT"), 0);
final RexNode condition = and(eq(input, pst), eq(input, gmt));
final RexNode simplified =
simplify.withParanoid(false).simplifyUnknownAs(condition, RexUnknownAs.FALSE);

assertThat(simplified.isAlwaysFalse(), is(false));
}

/** Test case for <a href="https://issues.apache.org/jira/browse/CALCITE-7526">[CALCITE-7526]
* Incorrect TIMESTAMP WITH TIME ZONE produces wrong error message</a>. */
@Test void testMalformedTimezone() {
Expand Down
Loading