| /** |
| * 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. |
| **/ |
| |
| #include "storage/CompressedStoreUtil.hpp" |
| |
| #include <cmath> |
| #include <cstddef> |
| #include <cstdint> |
| #include <limits> |
| #include <type_traits> |
| #include <utility> |
| |
| #include "catalog/CatalogRelationSchema.hpp" |
| #include "catalog/CatalogTypedefs.hpp" |
| #include "compression/CompressionDictionary.hpp" |
| #include "expressions/predicate/ComparisonPredicate.hpp" |
| #include "expressions/predicate/Predicate.hpp" |
| #include "expressions/scalar/Scalar.hpp" |
| #include "expressions/scalar/ScalarAttribute.hpp" |
| #include "types/DoubleType.hpp" |
| #include "types/FloatType.hpp" |
| #include "types/IntType.hpp" |
| #include "types/LongType.hpp" |
| #include "types/Type.hpp" |
| #include "types/TypedValue.hpp" |
| #include "types/TypeID.hpp" |
| #include "types/operations/comparisons/Comparison.hpp" |
| #include "types/operations/comparisons/ComparisonID.hpp" |
| #include "utility/Macros.hpp" |
| |
| #include "glog/logging.h" |
| |
| namespace quickstep { |
| |
| PredicateTransformResult CompressedAttributePredicateTransformer::TransformPredicateOnCompressedAttribute( |
| const CatalogRelationSchema &relation, |
| const ComparisonPredicate &predicate, |
| const CompressionDictionary *dictionary, |
| const std::int64_t maximum_truncated_value) { |
| // The predicate must be a comparison of a compressed attribute with |
| // a literal. |
| DCHECK(predicate.isAttributeLiteralComparisonPredicate()); |
| |
| const CatalogAttribute *comparison_attribute = nullptr; |
| bool left_literal = false; |
| if (predicate.getLeftOperand().hasStaticValue()) { |
| DCHECK(predicate.getRightOperand().getDataSource() == Scalar::kAttribute); |
| comparison_attribute |
| = &(static_cast<const ScalarAttribute&>(predicate.getRightOperand()).getAttribute()); |
| left_literal = true; |
| } else { |
| DCHECK(predicate.getLeftOperand().getDataSource() == Scalar::kAttribute); |
| comparison_attribute |
| = &(static_cast<const ScalarAttribute&>(predicate.getLeftOperand()).getAttribute()); |
| left_literal = false; |
| } |
| |
| DCHECK_EQ(relation.getID(), comparison_attribute->getParent().getID()); |
| TypedValue comparison_literal; |
| const Type *comparison_literal_type; |
| if (left_literal) { |
| comparison_literal = predicate.getLeftOperand().getStaticValue().makeReferenceToThis(); |
| comparison_literal_type = &predicate.getLeftOperand().getType(); |
| } else { |
| comparison_literal = predicate.getRightOperand().getStaticValue().makeReferenceToThis(); |
| comparison_literal_type = &predicate.getRightOperand().getType(); |
| } |
| |
| if (comparison_literal.isNull()) { |
| // Comparisons with null are always false. |
| return PredicateTransformResult(PredicateTransformResultType::kNone); |
| } |
| |
| if (predicate.getComparison().getComparisonID() == ComparisonID::kEqual) { |
| return TransformEqualPredicateOnCompressedAttribute(comparison_literal, |
| *comparison_literal_type, |
| dictionary, |
| maximum_truncated_value); |
| } else if (predicate.getComparison().getComparisonID() == ComparisonID::kNotEqual) { |
| return TransformNotEqualPredicateOnCompressedAttribute(comparison_literal, |
| *comparison_literal_type, |
| dictionary, |
| maximum_truncated_value); |
| } else { |
| if (left_literal) { |
| switch (predicate.getComparison().getComparisonID()) { |
| case ComparisonID::kLess: |
| return TransformOtherComparisonPredicateOnCompressedAttribute(ComparisonID::kGreater, |
| comparison_literal, |
| *comparison_literal_type, |
| dictionary, |
| maximum_truncated_value); |
| case ComparisonID::kLessOrEqual: |
| return TransformOtherComparisonPredicateOnCompressedAttribute(ComparisonID::kGreaterOrEqual, |
| comparison_literal, |
| *comparison_literal_type, |
| dictionary, |
| maximum_truncated_value); |
| case ComparisonID::kGreater: |
| return TransformOtherComparisonPredicateOnCompressedAttribute(ComparisonID::kLess, |
| comparison_literal, |
| *comparison_literal_type, |
| dictionary, |
| maximum_truncated_value); |
| case ComparisonID::kGreaterOrEqual: |
| return TransformOtherComparisonPredicateOnCompressedAttribute(ComparisonID::kLessOrEqual, |
| comparison_literal, |
| *comparison_literal_type, |
| dictionary, |
| maximum_truncated_value); |
| default: |
| FATAL_ERROR("Unexpected ComparisonID in " |
| "CompressedAttributePredicateTransformer::getMatchesForPredicate()"); |
| } |
| } else { |
| return TransformOtherComparisonPredicateOnCompressedAttribute( |
| predicate.getComparison().getComparisonID(), |
| comparison_literal, |
| *comparison_literal_type, |
| dictionary, |
| maximum_truncated_value); |
| } |
| } |
| } |
| |
| namespace { |
| |
| // Helper class for figuring out information about comparisons with truncated |
| // values. Has different partial specializations for integer types and |
| // floating-point types. |
| template <typename NumericLiteralT, typename Enable = void> |
| class TruncationHelper { |
| }; |
| |
| // Partial specialization for integer types. |
| template <typename NumericLiteralT> |
| class TruncationHelper< |
| NumericLiteralT, |
| typename std::enable_if<std::is_integral<NumericLiteralT>::value>::type> { |
| public: |
| // Whether an int64_t represents 'right_value' exactly. For Int and Long, |
| // this is always true. |
| static bool LongIsExact(const TypedValue &right_literal) { |
| return true; |
| } |
| |
| // Get 'right_literal' truncated to a code. |
| static std::uint32_t GetAsCode(const TypedValue &right_literal) { |
| return right_literal.getLiteral<NumericLiteralT>(); |
| } |
| |
| // Get the effective literal value to use in place of 'right_literal' when |
| // evaluating the comparison specified by 'comp'. Since LongIsExact() is |
| // always true for integer types, no adjustment is needed. |
| static std::int64_t GetEffectiveLiteralForComparison(const ComparisonID comp, |
| const TypedValue &right_literal) { |
| return right_literal.getLiteral<NumericLiteralT>(); |
| } |
| |
| // Determine whether 'value' is in the range of truncated codes between 0 and |
| // 'maximum_truncated_value'. |
| static bool ValueInRange(const TypedValue &value, |
| const std::int64_t maximum_truncated_value) { |
| const std::int64_t value_as_int64 = value.getLiteral<NumericLiteralT>(); |
| return (value_as_int64 >= 0) && (value_as_int64 <= maximum_truncated_value); |
| } |
| }; |
| |
| // Partial specialization for floating-point types. |
| template <typename NumericLiteralT> |
| class TruncationHelper< |
| NumericLiteralT, |
| typename std::enable_if<std::is_floating_point<NumericLiteralT>::value>::type> { |
| public: |
| // Whether an int64_t represents 'right_value' exactly. For floating-point |
| // types, 'right_value' may have a fractional part or a large exponent that |
| // makes it out-of-range. |
| static bool LongIsExact(const TypedValue &right_literal) { |
| const double value_as_double = right_literal.getLiteral<NumericLiteralT>(); |
| return value_as_double == static_cast<std::int64_t>(value_as_double); |
| } |
| |
| // Get 'right_literal' truncated to a code. |
| static std::uint32_t GetAsCode(const TypedValue &right_literal) { |
| return right_literal.getLiteral<NumericLiteralT>(); |
| } |
| |
| // Get the effective literal value to use in place of 'right_literal' when |
| // evaluating the comparison specified by 'comp'. If LongIsExact() is true, |
| // then no adjustment is needed. Otherwise, the value is rounded up or down |
| // as needed to make the comparison evaluate correctly. |
| static std::int64_t GetEffectiveLiteralForComparison(const ComparisonID comp, |
| const TypedValue &right_literal) { |
| const double value_as_double = right_literal.getLiteral<NumericLiteralT>(); |
| const std::int64_t value_as_int64 = value_as_double; |
| if (value_as_double == value_as_int64) { |
| // The value has no fractional part, so no adjustment is needed. |
| return value_as_int64; |
| } |
| |
| switch (comp) { |
| case ComparisonID::kLess: |
| case ComparisonID::kGreaterOrEqual: |
| return std::ceil(value_as_double); |
| case ComparisonID::kLessOrEqual: |
| case ComparisonID::kGreater: |
| return std::floor(value_as_double); |
| default: { |
| LOG(FATAL) << "Unexpected ComparisonID (not <, <=, >, or >=)"; |
| } |
| } |
| } |
| |
| // Determine whether 'value' is in the range of truncated codes between 0 and |
| // 'maximum_truncated_value'. |
| static bool ValueInRange(const TypedValue &value, |
| const std::int64_t maximum_truncated_value) { |
| const NumericLiteralT value_literal = value.getLiteral<NumericLiteralT>(); |
| return (value_literal >= 0) && (value_literal <= maximum_truncated_value); |
| } |
| }; |
| |
| } // namespace |
| |
| std::int64_t CompressedAttributePredicateTransformer::GetEffectiveLiteralValueForComparisonWithTruncatedAttribute( |
| const ComparisonID comp, |
| const TypedValue &right_literal, |
| const Type &right_literal_type) { |
| switch (right_literal_type.getTypeID()) { |
| case kInt: |
| return TruncationHelper<IntType::cpptype>::GetEffectiveLiteralForComparison( |
| comp, |
| right_literal); |
| case kLong: |
| return TruncationHelper<LongType::cpptype>::GetEffectiveLiteralForComparison( |
| comp, |
| right_literal); |
| case kFloat: |
| return TruncationHelper<FloatType::cpptype>::GetEffectiveLiteralForComparison( |
| comp, |
| right_literal); |
| case kDouble: |
| return TruncationHelper<DoubleType::cpptype>::GetEffectiveLiteralForComparison( |
| comp, |
| right_literal); |
| default: { |
| LOG(FATAL) << "Type not recognized as numeric: " << right_literal_type.getName(); |
| } |
| } |
| } |
| |
| bool CompressedAttributePredicateTransformer::CompressedComparisonIsAlwaysTrueForTruncatedAttribute( |
| const ComparisonID comp, |
| const TypedValue &right_literal, |
| const Type &right_literal_type, |
| const std::int64_t maximum_truncated_value) { |
| // First, check equality and inequality. |
| switch (comp) { |
| case ComparisonID::kEqual: |
| return false; |
| case ComparisonID::kNotEqual: |
| // When comparing against truncated codes, not-equal is always true if |
| // 'right_literal' has a fractional part, or if it is less than zero or |
| // greater than the largest truncated value. |
| switch (right_literal_type.getTypeID()) { |
| case kInt: { |
| return (!TruncationHelper<IntType::cpptype>::LongIsExact(right_literal)) |
| || (!TruncationHelper<IntType::cpptype>::ValueInRange(right_literal, |
| maximum_truncated_value)); |
| } |
| case kLong: { |
| return (!TruncationHelper<LongType::cpptype>::LongIsExact(right_literal)) |
| || (!TruncationHelper<LongType::cpptype>::ValueInRange(right_literal, |
| maximum_truncated_value)); |
| } |
| case kFloat: { |
| return (!TruncationHelper<FloatType::cpptype>::LongIsExact(right_literal)) |
| || (!TruncationHelper<FloatType::cpptype>::ValueInRange(right_literal, |
| maximum_truncated_value)); |
| } |
| case kDouble: { |
| return (!TruncationHelper<DoubleType::cpptype>::LongIsExact(right_literal)) |
| || (!TruncationHelper<DoubleType::cpptype>::ValueInRange(right_literal, |
| maximum_truncated_value)); |
| } |
| default: { |
| LOG(FATAL) << "Type not recognized as numeric: " << right_literal_type.getName(); |
| } |
| } |
| default: |
| break; |
| } |
| |
| std::int64_t effective_literal |
| = GetEffectiveLiteralValueForComparisonWithTruncatedAttribute(comp, |
| right_literal, |
| right_literal_type); |
| |
| switch (comp) { |
| case ComparisonID::kLess: |
| if (effective_literal > maximum_truncated_value) { |
| return true; |
| } else { |
| return false; |
| } |
| case ComparisonID::kLessOrEqual: |
| if (effective_literal >= maximum_truncated_value) { |
| return true; |
| } else { |
| return false; |
| } |
| case ComparisonID::kGreater: |
| if (effective_literal < 0) { |
| return true; |
| } else { |
| return false; |
| } |
| case ComparisonID::kGreaterOrEqual: |
| if (effective_literal <= 0) { |
| return true; |
| } else { |
| return false; |
| } |
| default: |
| FATAL_ERROR("Unexpected ComparisonID in CompressedAttributePredicateTransformer::" |
| "CompressedComparisonIsAlwaysTrueForTruncatedAttribute()"); |
| } |
| } |
| |
| bool CompressedAttributePredicateTransformer::CompressedComparisonIsAlwaysFalseForTruncatedAttribute( |
| const ComparisonID comp, |
| const TypedValue &right_literal, |
| const Type &right_literal_type, |
| const std::int64_t maximum_truncated_value) { |
| // First, check equality and inequality. |
| switch (comp) { |
| case ComparisonID::kEqual: |
| // When comparing against truncated codes, equal is always false if |
| // 'right_literal' has a fractional part, or if it is less than zero or |
| // greater than the largest truncated value. |
| switch (right_literal_type.getTypeID()) { |
| case kInt: { |
| return (!TruncationHelper<IntType::cpptype>::LongIsExact(right_literal)) |
| || (!TruncationHelper<IntType::cpptype>::ValueInRange(right_literal, |
| maximum_truncated_value)); |
| } |
| case kLong: { |
| return (!TruncationHelper<LongType::cpptype>::LongIsExact(right_literal)) |
| || (!TruncationHelper<LongType::cpptype>::ValueInRange(right_literal, |
| maximum_truncated_value)); |
| } |
| case kFloat: { |
| return (!TruncationHelper<FloatType::cpptype>::LongIsExact(right_literal)) |
| || (!TruncationHelper<FloatType::cpptype>::ValueInRange(right_literal, |
| maximum_truncated_value)); |
| } |
| case kDouble: { |
| return (!TruncationHelper<DoubleType::cpptype>::LongIsExact(right_literal)) |
| || (!TruncationHelper<DoubleType::cpptype>::ValueInRange(right_literal, |
| maximum_truncated_value)); |
| } |
| default: { |
| LOG(FATAL) << "Type not recognized as numeric: " << right_literal_type.getName(); |
| } |
| } |
| case ComparisonID::kNotEqual: |
| return false; |
| default: |
| break; |
| } |
| |
| std::int64_t effective_literal |
| = GetEffectiveLiteralValueForComparisonWithTruncatedAttribute(comp, |
| right_literal, |
| right_literal_type); |
| switch (comp) { |
| case ComparisonID::kLess: |
| if (effective_literal <= 0) { |
| return true; |
| } else { |
| return false; |
| } |
| case ComparisonID::kLessOrEqual: |
| if (effective_literal < 0) { |
| return true; |
| } else { |
| return false; |
| } |
| case ComparisonID::kGreater: |
| if (effective_literal >= maximum_truncated_value) { |
| return true; |
| } else { |
| return false; |
| } |
| case ComparisonID::kGreaterOrEqual: |
| if (effective_literal > maximum_truncated_value) { |
| return true; |
| } else { |
| return false; |
| } |
| default: |
| FATAL_ERROR("Unexpected ComparisonID in CompressedAttributePredicateTransformer::" |
| "CompressedComparisonIsAlwaysFalseForTruncatedAttribute()"); |
| } |
| } |
| |
| PredicateTransformResult CompressedAttributePredicateTransformer |
| ::TransformEqualPredicateOnCompressedAttribute( |
| const TypedValue &right_literal, |
| const Type &right_literal_type, |
| const CompressionDictionary *dictionary, |
| const std::int64_t maximum_truncated_value) { |
| std::uint32_t match_code; |
| if (dictionary) { |
| match_code = dictionary->getCodeForTypedValue(right_literal, right_literal_type); |
| if (match_code == dictionary->numberOfCodes()) { |
| return PredicateTransformResult(PredicateTransformResultType::kNone); |
| } |
| } else { |
| if (CompressedComparisonIsAlwaysFalseForTruncatedAttribute(ComparisonID::kEqual, |
| right_literal, |
| right_literal_type, |
| maximum_truncated_value)) { |
| return PredicateTransformResult(PredicateTransformResultType::kNone); |
| } |
| // At this point, we know that 'right_literal' should not have a fractional |
| // part and should be in the range of truncated codes. |
| switch (right_literal_type.getTypeID()) { |
| case kInt: |
| DCHECK(TruncationHelper<IntType::cpptype>::LongIsExact(right_literal)); |
| match_code = TruncationHelper<IntType::cpptype>::GetAsCode(right_literal); |
| break; |
| case kLong: |
| DCHECK(TruncationHelper<LongType::cpptype>::LongIsExact(right_literal)); |
| match_code = TruncationHelper<LongType::cpptype>::GetAsCode(right_literal); |
| break; |
| case kFloat: |
| DCHECK(TruncationHelper<FloatType::cpptype>::LongIsExact(right_literal)); |
| match_code = TruncationHelper<FloatType::cpptype>::GetAsCode(right_literal); |
| break; |
| case kDouble: |
| DCHECK(TruncationHelper<DoubleType::cpptype>::LongIsExact(right_literal)); |
| match_code = TruncationHelper<DoubleType::cpptype>::GetAsCode(right_literal); |
| break; |
| default: |
| LOG(FATAL) << "Type not recognized as numeric: " << right_literal_type.getName(); |
| } |
| } |
| |
| return PredicateTransformResult(PredicateTransformResultType::kBasicComparison, |
| ComparisonID::kEqual, |
| match_code); |
| } |
| |
| PredicateTransformResult CompressedAttributePredicateTransformer |
| ::TransformNotEqualPredicateOnCompressedAttribute( |
| const TypedValue &right_literal, |
| const Type &right_literal_type, |
| const CompressionDictionary *dictionary, |
| const std::int64_t maximum_truncated_value) { |
| std::uint32_t match_code; |
| std::uint32_t null_code = std::numeric_limits<std::uint32_t>::max(); |
| if (dictionary) { |
| match_code = dictionary->getCodeForTypedValue(right_literal, right_literal_type); |
| null_code = dictionary->getNullCode(); |
| if (match_code == dictionary->numberOfCodes()) { |
| if (dictionary->containsNull()) { |
| return PredicateTransformResult(PredicateTransformResultType::kBasicComparison, |
| ComparisonID::kNotEqual, |
| null_code); |
| } else { |
| // Get all tuples. |
| return PredicateTransformResult(PredicateTransformResultType::kAll); |
| } |
| } |
| } else { |
| if (CompressedComparisonIsAlwaysTrueForTruncatedAttribute(ComparisonID::kNotEqual, |
| right_literal, |
| right_literal_type, |
| maximum_truncated_value)) { |
| return PredicateTransformResult(PredicateTransformResultType::kAll); |
| } |
| // At this point, we know that 'right_literal' should not have a fractional |
| // part and should be in the range of truncated codes. |
| switch (right_literal_type.getTypeID()) { |
| case kInt: |
| DCHECK(TruncationHelper<IntType::cpptype>::LongIsExact(right_literal)); |
| match_code = TruncationHelper<IntType::cpptype>::GetAsCode(right_literal); |
| break; |
| case kLong: |
| DCHECK(TruncationHelper<LongType::cpptype>::LongIsExact(right_literal)); |
| match_code = TruncationHelper<LongType::cpptype>::GetAsCode(right_literal); |
| break; |
| case kFloat: |
| DCHECK(TruncationHelper<FloatType::cpptype>::LongIsExact(right_literal)); |
| match_code = TruncationHelper<FloatType::cpptype>::GetAsCode(right_literal); |
| break; |
| case kDouble: |
| DCHECK(TruncationHelper<DoubleType::cpptype>::LongIsExact(right_literal)); |
| match_code = TruncationHelper<DoubleType::cpptype>::GetAsCode(right_literal); |
| break; |
| default: |
| LOG(FATAL) << "Type not recognized as numeric: " << right_literal_type.getName(); |
| } |
| } |
| |
| if (null_code == std::numeric_limits<std::uint32_t>::max()) { |
| return PredicateTransformResult(PredicateTransformResultType::kBasicComparison, |
| ComparisonID::kNotEqual, |
| match_code); |
| } else { |
| return PredicateTransformResult(PredicateTransformResultType::kBasicComparison, |
| ComparisonID::kNotEqual, |
| match_code, |
| null_code, |
| true); |
| } |
| } |
| |
| PredicateTransformResult CompressedAttributePredicateTransformer |
| ::TransformOtherComparisonPredicateOnCompressedAttribute( |
| const ComparisonID comp, |
| const TypedValue &right_literal, |
| const Type &right_literal_type, |
| const CompressionDictionary *dictionary, |
| const std::int64_t maximum_truncated_value) { |
| std::pair<std::uint32_t, std::uint32_t> match_range; |
| if (dictionary) { |
| match_range = dictionary->getLimitCodesForComparisonTyped(comp, |
| right_literal, |
| right_literal_type); |
| if (match_range.first >= match_range.second) { |
| // No matches. |
| return PredicateTransformResult(PredicateTransformResultType::kNone); |
| } |
| if (match_range.second == dictionary->numberOfCodes()) { |
| // This trick lets us skip an unnecessary comparison. |
| match_range.second = std::numeric_limits<std::uint32_t>::max(); |
| } |
| } else { |
| if (CompressedComparisonIsAlwaysTrueForTruncatedAttribute(comp, |
| right_literal, |
| right_literal_type, |
| maximum_truncated_value)) { |
| return PredicateTransformResult(PredicateTransformResultType::kAll); |
| } |
| if (CompressedComparisonIsAlwaysFalseForTruncatedAttribute(comp, |
| right_literal, |
| right_literal_type, |
| maximum_truncated_value)) { |
| return PredicateTransformResult(PredicateTransformResultType::kNone); |
| } |
| |
| std::int64_t effective_literal |
| = GetEffectiveLiteralValueForComparisonWithTruncatedAttribute(comp, |
| right_literal, |
| right_literal_type); |
| switch (comp) { |
| case ComparisonID::kLess: |
| match_range.first = 0; |
| match_range.second = effective_literal; |
| break; |
| case ComparisonID::kLessOrEqual: |
| match_range.first = 0; |
| match_range.second = effective_literal + 1; |
| break; |
| case ComparisonID::kGreater: |
| match_range.first = effective_literal + 1; |
| match_range.second = std::numeric_limits<std::uint32_t>::max(); |
| break; |
| case ComparisonID::kGreaterOrEqual: |
| match_range.first = effective_literal; |
| match_range.second = std::numeric_limits<std::uint32_t>::max(); |
| break; |
| default: |
| FATAL_ERROR("Unexpected ComparisonID in CompressedAttributePredicateTransformer::" |
| "TransformOtherComparisonPredicateOnCompressedAttribute()"); |
| } |
| } |
| |
| if (match_range.first == 0) { |
| if (match_range.second == std::numeric_limits<std::uint32_t>::max()) { |
| return PredicateTransformResult(PredicateTransformResultType::kAll); |
| } else { |
| return PredicateTransformResult(PredicateTransformResultType::kBasicComparison, |
| ComparisonID::kLess, |
| match_range.second); |
| } |
| } else if (match_range.second == std::numeric_limits<std::uint32_t>::max()) { |
| return PredicateTransformResult(PredicateTransformResultType::kBasicComparison, |
| ComparisonID::kGreaterOrEqual, |
| match_range.first); |
| } else { |
| return PredicateTransformResult(PredicateTransformResultType::kRangeComparison, |
| match_range.first, |
| match_range.second); |
| } |
| } |
| |
| } // namespace quickstep |