blob: e118294665a78cf0f5f362474d3b98fc96221e76 [file] [log] [blame]
/*
* Copyright (C) 2019 The Android Open Source Project
*
* Licensed 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.
*/
#ifndef SRC_TRACE_PROCESSOR_DB_COMPARE_H_
#define SRC_TRACE_PROCESSOR_DB_COMPARE_H_
#include <stdint.h>
#include <algorithm>
#include <optional>
#include "perfetto/ext/base/string_view.h"
#include "perfetto/trace_processor/basic_types.h"
namespace perfetto {
namespace trace_processor {
namespace compare {
// This file contains the de-facto impleemntation of all comparisions used by
// trace processor in every setting. All of this is centralised in one file to
// ensure both internal consistency with SQLite and consistency with SQLite.
// Compare a non-null numeric to a double; returns:
// * <0 if i < d,
// * >0 if i > d.
// * 0 otherwise
// This code matches the behaviour of sqlite3IntFloatCompare.
inline int LongToDouble(int64_t i, double d) {
// First check if we are out of range for a int64_t. We use the constants
// directly instead of using numeric_limits as the casts introduces rounding
// in the doubles as a double cannot exactly represent int64::max().
if (d >= 9223372036854775808.0)
return -1;
if (d < -9223372036854775808.0)
return 1;
// Then, try to compare in int64 space to try and keep as much precision as
// possible.
int64_t d_i = static_cast<int64_t>(d);
if (i < d_i)
return -1;
if (i > d_i)
return 1;
// Finally, try and compare in double space, sacrificing precision if
// necessary.
double i_d = static_cast<double>(i);
return (i_d < d) ? -1 : (i_d > d ? 1 : 0);
}
// Compares two non-null numeric values; returns:
// * <0 if a < b,
// * >0 if a > b.
// * 0 otherwise
// This code matches the behaviour of the inline code in the comparision path of
// sqlite3VdbeExec (for ints) and the behaviour of sqlite3MemCompare (for
// doubles).
template <typename T>
inline int Numeric(T a, T b) {
static_assert(std::is_arithmetic<T>::value,
"Numeric comparision performed with non-numeric type");
return a < b ? -1 : (a > b ? 1 : 0);
}
// Compares two non-null bytes values; returns:
// * <0 if a < b,
// * >0 if a > b.
// * 0 otherwise
// This code matches the behaviour of sqlite3BlobCompare.
inline int Bytes(const void* a, size_t a_n, const void* b, size_t b_n) {
int res = memcmp(a, b, std::min(a_n, b_n));
return res != 0 ? res
: static_cast<int>(static_cast<int64_t>(a_n) -
static_cast<int64_t>(b_n));
}
// Compares two non-null string values; returns:
// * <0 if a < b,
// * >0 if a > b.
// * 0 otherwise
// This code matches the behaviour of sqlite3BlobCompare which is called when
// there is no collation sequence defined in sqlite3MemCompare.
inline int String(base::StringView a, base::StringView b) {
PERFETTO_DCHECK(a.data() != nullptr);
PERFETTO_DCHECK(b.data() != nullptr);
return Bytes(a.data(), a.size(), b.data(), b.size());
}
// Compares two nullable numeric values; returns:
// * 0 if both a and b are null
// * <0 if a is null and b is non null
// * >0 if a is non null and b is null
// * <0 if a < b (a and b both non null)
// * >0 if a > b (a and b both non null)
// * 0 otherwise
// Should only be used for defining an ordering on value of type T. For filter
// functions, compare::Numeric should be used above after checking if the value
// is null.
// This method was defined from observing the behaviour of SQLite when sorting
// on columns containing nulls.
template <typename T>
inline int NullableNumeric(std::optional<T> a, std::optional<T> b) {
if (!a)
return b ? -1 : 0;
if (!b)
return 1;
return compare::Numeric(*a, *b);
}
// Compares two strings, either of which can be null; returns:
// * 0 if both a and b are null
// * <0 if a is null and b is non null
// * >0 if a is non null and b is null
// * <0 if a < b (a and b both non null)
// * >0 if a > b (a and b both non null)
// * 0 otherwise
// Should only be used for defining an ordering on value of type T. For filter
// functions, compare::String should be used above after checking if the value
// is null.
// This method was defined from observing the behaviour of SQLite when sorting
// on columns containing nulls.
inline int NullableString(base::StringView a, base::StringView b) {
if (!a.data())
return b.data() ? -1 : 0;
if (!b.data())
return 1;
return compare::String(a, b);
}
// Compares two SqlValue; returns:
// * <0 if a.type < b.type and a and b are not both numeric
// * >0 if a.type > b.type and a and b are not both numeric
// * <0 if a < b (a and b both non null and either of same type or numeric)
// * >0 if a > b (a and b both non null and either of same type or numeric)
// * 0 otherwise
// This code roughly matches the behaviour of the code in the comparision path
// of sqlite3VdbeExec except we are intentionally more strict than SQLite when
// it comes to casting between strings and numerics - we disallow moving between
// the two. We do allow comparing between doubles and longs however as doubles
// can easily appear by using constants in SQL even when intending to use longs.
inline int SqlValue(const SqlValue& a, const SqlValue& b) {
if (a.type != b.type) {
if (a.type == SqlValue::kLong && b.type == SqlValue::kDouble)
return compare::LongToDouble(a.long_value, b.double_value);
if (a.type == SqlValue::kDouble && b.type == SqlValue::kLong)
return -compare::LongToDouble(b.long_value, a.double_value);
return a.type - b.type;
}
switch (a.type) {
case SqlValue::Type::kLong:
return compare::Numeric(a.long_value, b.long_value);
case SqlValue::Type::kDouble:
return compare::Numeric(a.double_value, b.double_value);
case SqlValue::Type::kString:
return compare::String(a.string_value, b.string_value);
case SqlValue::Type::kBytes:
return compare::Bytes(a.bytes_value, a.bytes_count, b.bytes_value,
b.bytes_count);
case SqlValue::Type::kNull:
return 0;
}
PERFETTO_FATAL("For GCC");
}
// Implements a comparator for SqlValues to use for std algorithms.
// See documentation of compare::SqlValue for details of how this function
// works.
inline int SqlValueComparator(const trace_processor::SqlValue& a,
const trace_processor::SqlValue& b) {
return compare::SqlValue(a, b) < 0;
}
} // namespace compare
} // namespace trace_processor
} // namespace perfetto
#endif // SRC_TRACE_PROCESSOR_DB_COMPARE_H_