diff --git a/src/storage/predicate/column_predicate.cpp b/src/storage/predicate/column_predicate.cpp index 5e07e33abd..9f4c9af21a 100644 --- a/src/storage/predicate/column_predicate.cpp +++ b/src/storage/predicate/column_predicate.cpp @@ -36,12 +36,43 @@ static bool isColumnRef(ExpressionType type) { return type == ExpressionType::PROPERTY || type == ExpressionType::VARIABLE; } +static bool isIntegerUpTo64Bits(LogicalTypeID type) { + return LogicalTypeUtils::isIntegral(type) && type != LogicalTypeID::INT128 && + type != LogicalTypeID::UINT128; +} + +// The pushed-down predicate compares the column's stats, read as the cast's target type, with +// the literal. That is only right when the cast keeps every value and its order in the same +// stats slot: the same type, integer widening up to 64 bits (an unsigned value reads the same +// from the signed slot of a wider type) and FLOAT to DOUBLE. 128-bit targets are excluded because +// a negative 64-bit value is stored without sign extension into the high half. +static bool castKeepsStats(const LogicalType& from, const LogicalType& to) { + if (from == to) { + return true; + } + if (from.getLogicalTypeID() == LogicalTypeID::FLOAT) { + return to.getLogicalTypeID() == LogicalTypeID::DOUBLE; + } + if (!isIntegerUpTo64Bits(from.getLogicalTypeID()) || + !isIntegerUpTo64Bits(to.getLogicalTypeID())) { + return false; + } + auto fromSize = PhysicalTypeUtils::getFixedTypeSize(from.getPhysicalType()); + auto toSize = PhysicalTypeUtils::getFixedTypeSize(to.getPhysicalType()); + if (LogicalTypeUtils::isUnsigned(to)) { + return LogicalTypeUtils::isUnsigned(from) && fromSize <= toSize; + } + return LogicalTypeUtils::isUnsigned(from) ? fromSize < toSize : fromSize <= toSize; +} + static bool isCastedColumnRef(const Expression& expr) { if (expr.expressionType == ExpressionType::FUNCTION) { const auto& funcExpr = expr.constCast(); if (funcExpr.getFunction().name.starts_with("CAST")) { DASSERT(funcExpr.getNumChildren() > 0); - return isColumnRef(funcExpr.getChild(0)->expressionType); + const auto& child = *funcExpr.getChild(0); + return isColumnRef(child.expressionType) && + castKeepsStats(child.getDataType(), expr.getDataType()); } } return false; diff --git a/test/test_files/filter/cast.test b/test/test_files/filter/cast.test index f4eb0b0714..cc52cc3800 100644 --- a/test/test_files/filter/cast.test +++ b/test/test_files/filter/cast.test @@ -10,3 +10,121 @@ -STATEMENT match (c:Comment) where c.id = 687194777181 return c.browserUsed, c.content; ---- 1 Chrome|right + +-CASE ZoneMapCastedColumnNumericLiteral +-STATEMENT CREATE NODE TABLE T(id INT64 PRIMARY KEY, i INT64, i32 INT32, u32 UINT32, f DOUBLE, fl FLOAT, d DECIMAL(18,2), dt DATE, tms TIMESTAMP_MS); +---- ok +-STATEMENT CREATE (:T {id: 1, i: 30, i32: 30, u32: 30, f: 30.7, fl: 30.5, d: 5.00, dt: date('2020-01-01'), tms: CAST('2020-01-01 00:00:00' AS TIMESTAMP_MS)}); +---- ok +-STATEMENT CREATE (:T {id: 2, i: 40, i32: 40, u32: 4000000000, f: 40.2, fl: 40.5, d: 7.00, dt: date('2021-01-01'), tms: CAST('2021-01-01 00:00:00' AS TIMESTAMP_MS)}); +---- ok +-STATEMENT CREATE NODE TABLE N(id INT64 PRIMARY KEY, i INT64, i32 INT32); +---- ok +-STATEMENT CREATE (:N {id: 1, i: -5, i32: -20}); +---- ok +-STATEMENT CREATE (:N {id: 2, i: 40, i32: 40}); +---- ok +-LOG InMemory +-STATEMENT MATCH (n:T) WHERE n.i > 30.5 RETURN n.id; +---- 1 +2 +-STATEMENT MATCH (n:T) WHERE n.i = 40.0 RETURN n.id; +---- 1 +2 +-STATEMENT MATCH (n:T) WHERE n.i32 > 35.5 RETURN n.id; +---- 1 +2 +-STATEMENT MATCH (n:T) WHERE CAST(n.f AS INT64) = 31 RETURN n.id; +---- 1 +1 +-STATEMENT MATCH (n:T) WHERE CAST(n.d AS INT64) = 5 RETURN n.id; +---- 1 +1 +-STATEMENT MATCH (n:T) WHERE n.dt > timestamp('2020-06-01 00:00:00') RETURN n.id; +---- 1 +2 +-STATEMENT MATCH (n:T) WHERE n.tms > timestamp('2020-06-01 00:00:00') RETURN n.id; +---- 1 +2 +-STATEMENT MATCH (n:T) WHERE CAST(n.i32 AS INT64) = 40 RETURN n.id; +---- 1 +2 +-STATEMENT MATCH (n:T) WHERE n.u32 > -1 RETURN n.id ORDER BY n.id; +-CHECK_ORDER +---- 2 +1 +2 +-STATEMENT MATCH (n:T) WHERE n.u32 < 5000000000 RETURN n.id ORDER BY n.id; +-CHECK_ORDER +---- 2 +1 +2 +-STATEMENT MATCH (n:T) WHERE n.u32 > 5000000000 RETURN n.id; +---- 0 +-STATEMENT MATCH (n:T) WHERE n.fl > 40.25 RETURN n.id; +---- 1 +2 +-STATEMENT MATCH (n:N) WHERE n.i < 9223372036854775808 RETURN n.id ORDER BY n.id; +-CHECK_ORDER +---- 2 +1 +2 +-STATEMENT MATCH (n:N) WHERE CAST(n.i32 AS INT64) = -20 RETURN n.id; +---- 1 +1 +-STATEMENT MATCH (n:N) WHERE CAST(n.i32 AS INT64) <> 40 RETURN n.id; +---- 1 +1 +-STATEMENT CHECKPOINT; +---- ok +-LOG Checkpointed +-STATEMENT MATCH (n:T) WHERE n.i > 30.5 RETURN n.id; +---- 1 +2 +-STATEMENT MATCH (n:T) WHERE n.i = 40.0 RETURN n.id; +---- 1 +2 +-STATEMENT MATCH (n:T) WHERE n.i32 > 35.5 RETURN n.id; +---- 1 +2 +-STATEMENT MATCH (n:T) WHERE CAST(n.f AS INT64) = 31 RETURN n.id; +---- 1 +1 +-STATEMENT MATCH (n:T) WHERE CAST(n.d AS INT64) = 5 RETURN n.id; +---- 1 +1 +-STATEMENT MATCH (n:T) WHERE n.dt > timestamp('2020-06-01 00:00:00') RETURN n.id; +---- 1 +2 +-STATEMENT MATCH (n:T) WHERE n.tms > timestamp('2020-06-01 00:00:00') RETURN n.id; +---- 1 +2 +-STATEMENT MATCH (n:T) WHERE CAST(n.i32 AS INT64) = 40 RETURN n.id; +---- 1 +2 +-STATEMENT MATCH (n:T) WHERE n.u32 > -1 RETURN n.id ORDER BY n.id; +-CHECK_ORDER +---- 2 +1 +2 +-STATEMENT MATCH (n:T) WHERE n.u32 < 5000000000 RETURN n.id ORDER BY n.id; +-CHECK_ORDER +---- 2 +1 +2 +-STATEMENT MATCH (n:T) WHERE n.u32 > 5000000000 RETURN n.id; +---- 0 +-STATEMENT MATCH (n:T) WHERE n.fl > 40.25 RETURN n.id; +---- 1 +2 +-STATEMENT MATCH (n:N) WHERE n.i < 9223372036854775808 RETURN n.id ORDER BY n.id; +-CHECK_ORDER +---- 2 +1 +2 +-STATEMENT MATCH (n:N) WHERE CAST(n.i32 AS INT64) = -20 RETURN n.id; +---- 1 +1 +-STATEMENT MATCH (n:N) WHERE CAST(n.i32 AS INT64) <> 40 RETURN n.id; +---- 1 +1