From 6eb023b4b891de136f11aec69b0f02e6f192704e Mon Sep 17 00:00:00 2001 From: ik4rd Date: Fri, 29 May 2026 18:01:25 +0300 Subject: [PATCH 01/16] fix: GROUP BY fixing --- src/executor/operators_aggregate.cpp | 439 +++++++++++++++++++-------- tests/test_executor.cpp | 37 +++ 2 files changed, 350 insertions(+), 126 deletions(-) diff --git a/src/executor/operators_aggregate.cpp b/src/executor/operators_aggregate.cpp index 56c6620..4db07cc 100644 --- a/src/executor/operators_aggregate.cpp +++ b/src/executor/operators_aggregate.cpp @@ -1,7 +1,9 @@ #include #include #include +#include #include +#include #include #include @@ -77,6 +79,205 @@ struct AggBinding { std::unique_ptr state; }; +class CompactAggState { + public: + explicit CompactAggState(const PlannedAgg& aggregate) : type_(aggregate.input_type) { + if (aggregate.distinct || aggregate.function == nullptr) { + fallback_ = CreateAggState(aggregate); + return; + } + + const std::string name = ToUpperAscii(aggregate.function->canonical_name); + if (name == "COUNT") { + kind_ = Kind::Count; + } else if (name == "SUM") { + kind_ = Kind::Sum; + } else if (name == "AVG") { + kind_ = Kind::Avg; + } else if (name == "MIN") { + kind_ = Kind::Extremum; + is_min_ = true; + } else if (name == "MAX") { + kind_ = Kind::Extremum; + is_min_ = false; + } else { + fallback_ = CreateAggState(aggregate); + } + } + + void ConsumeValue(const std::string_view value) { + if (fallback_) { + fallback_->ConsumeValue(value); + return; + } + + switch (kind_) { + case Kind::Count: + ConsumeRow(); + return; + case Kind::Sum: + sum_ += ParseColumnValueAsInt128(type_, value); + return; + case Kind::Avg: + sum_ += ParseColumnValueAsInt128(type_, value); + ++count_; + return; + case Kind::Extremum: + ConsumeExtremum(value); + } + } + + void ConsumeInt128(const Int128 value) { + if (fallback_) { + fallback_->ConsumeInt128(value); + return; + } + + switch (kind_) { + case Kind::Count: + ConsumeRow(); + return; + case Kind::Sum: + sum_ += value; + return; + case Kind::Avg: + sum_ += value; + ++count_; + return; + case Kind::Extremum: + if (!typed_extremum_.has_value() || (is_min_ ? value < *typed_extremum_ : value > *typed_extremum_)) { + typed_extremum_ = value; + } + } + } + + void ConsumeRow() { + if (fallback_) { + fallback_->ConsumeRow(); + return; + } + + if (kind_ != Kind::Count) { + throw Error::InvalidState("executor", "aggregate requires a value"); + } + + ++count_; + } + + void ConsumeRows(const size_t count) { + if (fallback_) { + fallback_->ConsumeRows(count); + return; + } + + if (kind_ != Kind::Count) { + throw Error::InvalidState("executor", "aggregate requires a value"); + } + + count_ += count; + } + + std::string Finalize() const { + if (fallback_) { + return fallback_->Finalize(); + } + + switch (kind_) { + case Kind::Count: + return std::to_string(count_); + case Kind::Sum: + return Int128ToString(sum_); + case Kind::Avg: + return count_ == 0 ? "0" : Int128ToString(sum_ / static_cast(count_)); + case Kind::Extremum: + if (typed_extremum_.has_value()) { + return FormatInt128Value(type_, *typed_extremum_); + } + return string_extremum_.value_or(""); + } + + return {}; + } + + Int128 FinalizeInt(const ColumnType output_type) const { + if (fallback_) { + return ParseColumnValueAsInt128(output_type, fallback_->Finalize()); + } + + switch (kind_) { + case Kind::Count: + return count_; + case Kind::Sum: + return sum_; + case Kind::Avg: + return count_ == 0 ? 0 : sum_ / static_cast(count_); + case Kind::Extremum: + return typed_extremum_.value_or(0); + } + + return 0; + } + + private: + enum class Kind { + Count, + Sum, + Avg, + Extremum, + }; + + void ConsumeExtremum(const std::string_view value) { + if (IsNumericColumnType(type_) || type_ == ColumnType::Date || type_ == ColumnType::Timestamp || + type_ == ColumnType::Boolean || type_ == ColumnType::Character) { + ConsumeInt128(ParseColumnValueAsInt128(type_, value)); + return; + } + + if (!string_extremum_.has_value() || + (is_min_ ? value < std::string_view(*string_extremum_) : value > std::string_view(*string_extremum_))) { + string_extremum_ = std::string(value); + } + } + + Kind kind_ = Kind::Count; + ColumnType type_ = ColumnType::String; + bool is_min_ = true; + + Int128 sum_ = 0; + uint64_t count_ = 0; + + std::optional typed_extremum_; + std::optional string_extremum_; + std::unique_ptr fallback_; +}; + +void ConsumeCompactAggRow(const PlannedAgg& aggregate, const Batch& batch, const size_t row, CompactAggState& state) { + if (aggregate.direct_numeric_argument) { + state.ConsumeInt128(batch.ColumnAt(aggregate.column_index).ValueAsInt128(row) + + aggregate.direct_numeric_offset); + return; + } + + if (aggregate.argument_kind == AggArgumentKind::Column) { + if (aggregate.input_type != ColumnType::String) { + if (const auto typed_value = TryEvalTypedGroupKeyInt(aggregate.argument, batch, row); + typed_value.has_value()) { + state.ConsumeInt128(*typed_value); + return; + } + } + state.ConsumeValue(EvalExpr(aggregate.argument, batch, row)); + return; + } + + state.ConsumeRow(); +} + +struct FinalizedAggregateValues { + std::vector values; + std::vector int_values; +}; + struct GroupKeyComponent { ColumnType type = ColumnType::String; @@ -376,37 +577,30 @@ class GroupAggOperator final : public Operator { TypedGroupKey key = group_key_materializer_.Materialize(*batch, row, string_arena_); - size_t group_index = 0; - const auto it = group_indexes_.find(key); - if (it == group_indexes_.end()) { - group_index = groups_.size(); - group_indexes_.emplace(key, group_index); - - groups_.push_back(GroupState{ - .key_values = std::move(key.values), + auto it = groups_.find(key); + if (it == groups_.end()) { + GroupState group{ .states = CreateStates(), - .aggregate_values = {}, - .aggregate_int_values = {}, - .aggregate_values_finalized = false, - }); - } else { - group_index = it->second; + .finalized_aggregates = nullptr, + .ordinal = next_group_ordinal_++, + }; + it = groups_.emplace(std::move(key), std::move(group)).first; } - GroupState& group = groups_[group_index]; + GroupState& group = it->second; for (size_t i = 0; i < bindings_.size(); ++i) { - ConsumeAggRow(bindings_[i].aggregate, *batch, row, *group.states[i]); + ConsumeCompactAggRow(bindings_[i].aggregate, *batch, row, group.states[i]); } } } const Schema schema = BuildSelectOutputSchema(select_items_); - const std::vector output_order = BuildOutputOrder(); + const std::vector output_order = BuildOutputOrder(); Batch result(schema, groups_.size()); - for (const size_t group_index : output_order) { - if (GroupMatchesHaving(groups_[group_index], schema)) { - AppendGroupToBatch(groups_[group_index], result); + for (const GroupEntry* entry : output_order) { + if (GroupMatchesHaving(entry->first, entry->second, schema)) { + AppendGroupToBatch(entry->first, entry->second, result); } } @@ -415,83 +609,86 @@ class GroupAggOperator final : public Operator { private: struct GroupState { - std::vector key_values; - - std::vector> states; + std::vector states; - mutable std::vector aggregate_values; - mutable std::vector aggregate_int_values; - - mutable bool aggregate_values_finalized = false; + mutable std::unique_ptr finalized_aggregates; + size_t ordinal = 0; }; + using GroupMap = std::unordered_map; + using GroupEntry = GroupMap::value_type; + struct GroupAggBinding { PlannedAgg aggregate; }; - std::vector> CreateStates() const { - std::vector> states; + std::vector CreateStates() const { + std::vector states; states.reserve(bindings_.size()); for (const auto& binding : bindings_) { - states.push_back(CreateAggState(binding.aggregate)); + states.emplace_back(binding.aggregate); } return states; } - void FinalizeAggregateValues(const GroupState& group) const { - if (group.aggregate_values_finalized) { - return; + const FinalizedAggregateValues& FinalizeAggregateValues(const GroupState& group) const { + if (group.finalized_aggregates) { + return *group.finalized_aggregates; } - group.aggregate_values.reserve(group.states.size()); - group.aggregate_int_values.reserve(group.states.size()); + auto finalized = std::make_unique(); + finalized->values.reserve(group.states.size()); + finalized->int_values.reserve(group.states.size()); for (size_t i = 0; i < group.states.size(); ++i) { - std::string value = group.states[i]->Finalize(); + std::string value = group.states[i].Finalize(); const ColumnType type = AggregateOutputType(bindings_[i].aggregate); - group.aggregate_int_values.push_back(type == ColumnType::String ? 0 - : ParseColumnValueAsInt128(type, value)); - group.aggregate_values.push_back(std::move(value)); + finalized->int_values.push_back(type == ColumnType::String ? 0 : ParseColumnValueAsInt128(type, value)); + finalized->values.push_back(std::move(value)); } - group.aggregate_values_finalized = true; + group.finalized_aggregates = std::move(finalized); + return *group.finalized_aggregates; } - void AppendGroupToBatch(const GroupState& group, const Batch& batch) const { - FinalizeAggregateValues(group); + void AppendGroupToBatch(const TypedGroupKey& key, const GroupState& group, const Batch& batch) const { + const FinalizedAggregateValues& finalized = FinalizeAggregateValues(group); for (size_t column = 0; column < select_items_.size(); ++column) { const PlannedSelectItem& item = select_items_[column]; if (item.kind == SelectItemKind::GroupKey) { - batch.AppendValueFromString(column, FormatGroupKeyValue(group.key_values[item.index])); + batch.AppendValueFromString(column, FormatGroupKeyValue(key.values[item.index])); } else { - batch.AppendValueFromString(column, group.aggregate_values[item.index]); + batch.AppendValueFromString(column, finalized.values[item.index]); } } } - bool GroupMatchesHaving(const GroupState& group, const Schema& schema) const { + bool GroupMatchesHaving(const TypedGroupKey& key, const GroupState& group, const Schema& schema) const { if (!having_) { return true; } const Batch row(schema, 1); - AppendGroupToBatch(group, row); + AppendGroupToBatch(key, group, row); return EvaluatePredicate(having_, row, 0); } - std::vector BuildOutputOrder() const { - std::vector order; + std::vector BuildOutputOrder() const { + std::vector order; order.reserve(groups_.size()); - for (size_t i = 0; i < groups_.size(); ++i) { - order.push_back(i); + for (const auto& entry : groups_) { + order.push_back(&entry); } if (!sort_by_group_keys_) { + std::ranges::sort(order, [](const GroupEntry* lhs, const GroupEntry* rhs) { + return lhs->second.ordinal < rhs->second.ordinal; + }); return order; } @@ -507,19 +704,17 @@ class GroupAggOperator final : public Operator { return order; } - std::ranges::sort(order, [&](const size_t lhs_index, const size_t rhs_index) { - const GroupState& lhs = groups_[lhs_index]; - const GroupState& rhs = groups_[rhs_index]; + std::ranges::sort(order, [&](const GroupEntry* lhs, const GroupEntry* rhs) { for (const size_t column : group_key_columns) { const PlannedSelectItem& item = select_items_[column]; const std::strong_ordering comparison = - CompareGroupKeyValue(lhs.key_values[item.index], rhs.key_values[item.index]); + CompareGroupKeyValue(lhs->first.values[item.index], rhs->first.values[item.index]); if (comparison != 0) { return comparison < 0; } } - return false; + return lhs->second.ordinal < rhs->second.ordinal; }); return order; @@ -535,8 +730,8 @@ class GroupAggOperator final : public Operator { PredicatePtr having_; StringArena string_arena_; - std::unordered_map group_indexes_; - std::vector groups_; + GroupMap groups_; + size_t next_group_ordinal_ = 0; bool sort_by_group_keys_ = false; bool returned_ = false; @@ -577,36 +772,29 @@ class GroupAggTopKOperator final : public Operator { TypedGroupKey key = group_key_materializer_.Materialize(*batch, row, string_arena_); - size_t group_index = 0; - const auto it = group_indexes_.find(key); - if (it == group_indexes_.end()) { - group_index = groups_.size(); - group_indexes_.emplace(key, group_index); - - groups_.push_back(GroupState{ - .key_values = std::move(key.values), + auto it = groups_.find(key); + if (it == groups_.end()) { + GroupState group{ .states = CreateStates(), - .aggregate_values = {}, - .aggregate_int_values = {}, - .aggregate_values_finalized = false, - }); - } else { - group_index = it->second; + .finalized_aggregates = nullptr, + .ordinal = next_group_ordinal_++, + }; + it = groups_.emplace(std::move(key), std::move(group)).first; } - GroupState& group = groups_[group_index]; + GroupState& group = it->second; for (size_t i = 0; i < bindings_.size(); ++i) { - ConsumeAggRow(bindings_[i].aggregate, *batch, row, *group.states[i]); + ConsumeCompactAggRow(bindings_[i].aggregate, *batch, row, group.states[i]); } } } const Schema schema = BuildSelectOutputSchema(select_items_); - const std::vector top_groups = BuildTopGroupIndexes(); + const std::vector top_groups = BuildTopGroups(); Batch result(schema, top_groups.size()); - for (const size_t group_index : top_groups) { - AppendGroupToBatch(groups_[group_index], result); + for (const GroupEntry* entry : top_groups) { + AppendGroupToBatch(entry->first, entry->second, result); } return result; @@ -614,66 +802,63 @@ class GroupAggTopKOperator final : public Operator { private: struct GroupState { - std::vector key_values; - - std::vector> states; - - mutable std::vector aggregate_values; - mutable std::vector aggregate_int_values; + std::vector states; - mutable bool aggregate_values_finalized = false; + mutable std::unique_ptr finalized_aggregates; + size_t ordinal = 0; }; + using GroupMap = std::unordered_map; + using GroupEntry = GroupMap::value_type; + struct GroupAggBinding { PlannedAgg aggregate; }; - std::vector> CreateStates() const { - std::vector> states; + std::vector CreateStates() const { + std::vector states; states.reserve(bindings_.size()); for (const auto& binding : bindings_) { - states.push_back(CreateAggState(binding.aggregate)); + states.emplace_back(binding.aggregate); } return states; } - void FinalizeAggregateValues(const GroupState& group) const { - if (group.aggregate_values_finalized) { - return; + const FinalizedAggregateValues& FinalizeAggregateValues(const GroupState& group) const { + if (group.finalized_aggregates) { + return *group.finalized_aggregates; } - group.aggregate_values.reserve(group.states.size()); - group.aggregate_int_values.reserve(group.states.size()); + auto finalized = std::make_unique(); + finalized->values.reserve(group.states.size()); + finalized->int_values.reserve(group.states.size()); for (size_t i = 0; i < group.states.size(); ++i) { - std::string value = group.states[i]->Finalize(); + std::string value = group.states[i].Finalize(); const ColumnType type = AggregateOutputType(bindings_[i].aggregate); - group.aggregate_int_values.push_back(type == ColumnType::String ? 0 - : ParseColumnValueAsInt128(type, value)); - group.aggregate_values.push_back(std::move(value)); + finalized->int_values.push_back(type == ColumnType::String ? 0 : ParseColumnValueAsInt128(type, value)); + finalized->values.push_back(std::move(value)); } - group.aggregate_values_finalized = true; + group.finalized_aggregates = std::move(finalized); + return *group.finalized_aggregates; } - bool OrdersBefore(const size_t lhs_index, const size_t rhs_index) const { - const GroupState& lhs = groups_[lhs_index]; - const GroupState& rhs = groups_[rhs_index]; - + bool OrdersBefore(const GroupEntry* lhs, const GroupEntry* rhs) const { for (const PlannedOrderBy& order : order_by_) { const PlannedSelectItem& item = select_items_[order.result_column_index]; std::strong_ordering comparison = std::strong_ordering::equal; if (item.kind == SelectItemKind::GroupKey) { - comparison = CompareGroupKeyValue(lhs.key_values[item.index], rhs.key_values[item.index]); + comparison = CompareGroupKeyValue(lhs->first.values[item.index], rhs->first.values[item.index]); } else { - FinalizeAggregateValues(lhs); - FinalizeAggregateValues(rhs); - comparison = item.output_type == ColumnType::String - ? lhs.aggregate_values[item.index] <=> rhs.aggregate_values[item.index] - : lhs.aggregate_int_values[item.index] <=> rhs.aggregate_int_values[item.index]; + comparison = + item.output_type == ColumnType::String + ? lhs->second.states[item.index].Finalize() <=> rhs->second.states[item.index].Finalize() + : lhs->second.states[item.index].FinalizeInt(item.output_type) <=> + rhs->second.states[item.index].FinalizeInt(item.output_type); } if (comparison != 0) { @@ -681,44 +866,46 @@ class GroupAggTopKOperator final : public Operator { } } - return lhs_index < rhs_index; + return lhs->second.ordinal < rhs->second.ordinal; } - void AppendGroupToBatch(const GroupState& group, const Batch& batch) const { - FinalizeAggregateValues(group); + void AppendGroupToBatch(const TypedGroupKey& key, const GroupState& group, const Batch& batch) const { + const FinalizedAggregateValues& finalized = FinalizeAggregateValues(group); for (size_t column = 0; column < select_items_.size(); ++column) { const PlannedSelectItem& item = select_items_[column]; if (item.kind == SelectItemKind::GroupKey) { - batch.AppendValueFromString(column, FormatGroupKeyValue(group.key_values[item.index])); + batch.AppendValueFromString(column, FormatGroupKeyValue(key.values[item.index])); } else { - batch.AppendValueFromString(column, group.aggregate_values[item.index]); + batch.AppendValueFromString(column, finalized.values[item.index]); } } } - std::vector BuildTopGroupIndexes() const { - std::vector top_groups; + std::vector BuildTopGroups() const { + std::vector top_groups; top_groups.reserve(limit_); - for (size_t i = 0; i < groups_.size(); ++i) { + for (const auto& entry : groups_) { + const GroupEntry* group = &entry; if (top_groups.size() < limit_) { - top_groups.push_back(i); - std::ranges::push_heap(top_groups, - [&](const size_t lhs, const size_t rhs) { return OrdersBefore(lhs, rhs); }); + top_groups.push_back(group); + std::ranges::push_heap( + top_groups, [&](const GroupEntry* lhs, const GroupEntry* rhs) { return OrdersBefore(lhs, rhs); }); continue; } - if (OrdersBefore(i, top_groups.front())) { - std::ranges::pop_heap(top_groups, - [&](const size_t lhs, const size_t rhs) { return OrdersBefore(lhs, rhs); }); - top_groups.back() = i; - std::ranges::push_heap(top_groups, - [&](const size_t lhs, const size_t rhs) { return OrdersBefore(lhs, rhs); }); + if (OrdersBefore(group, top_groups.front())) { + std::ranges::pop_heap( + top_groups, [&](const GroupEntry* lhs, const GroupEntry* rhs) { return OrdersBefore(lhs, rhs); }); + top_groups.back() = group; + std::ranges::push_heap( + top_groups, [&](const GroupEntry* lhs, const GroupEntry* rhs) { return OrdersBefore(lhs, rhs); }); } } - std::ranges::sort(top_groups, [&](const size_t lhs, const size_t rhs) { return OrdersBefore(lhs, rhs); }); + std::ranges::sort(top_groups, + [&](const GroupEntry* lhs, const GroupEntry* rhs) { return OrdersBefore(lhs, rhs); }); return top_groups; } @@ -735,8 +922,8 @@ class GroupAggTopKOperator final : public Operator { size_t limit_ = 0; StringArena string_arena_; - std::unordered_map group_indexes_; - mutable std::vector groups_; + mutable GroupMap groups_; + size_t next_group_ordinal_ = 0; bool returned_ = false; }; diff --git a/tests/test_executor.cpp b/tests/test_executor.cpp index 3dd9b80..53ad147 100644 --- a/tests/test_executor.cpp +++ b/tests/test_executor.cpp @@ -195,6 +195,43 @@ TEST(executor, supports_multiple_aggregates_with_alias_and_limit) { EXPECT_EQ(actual_rows, expected_rows); } +TEST(executor, supports_top_k_group_by_multiple_numeric_keys_with_compact_aggregates) { + const TempFile schema_file("executor_schema"); + const TempFile data_file("executor_data"); + const TempFile columnar_file("executor_columnar"); + + WriteRows(schema_file.Path(), { + {"WatchID", "int64"}, + {"ClientIP", "int32"}, + {"IsRefresh", "int16"}, + {"ResolutionWidth", "int16"}, + }); + WriteRows(data_file.Path(), { + {"10", "1", "1", "100"}, + {"10", "1", "0", "200"}, + {"10", "1", "1", "300"}, + {"11", "1", "1", "400"}, + {"11", "1", "0", "600"}, + {"10", "2", "1", "800"}, + }); + ConvertCsvToColumnar(schema_file.Path(), data_file.Path(), columnar_file.Path(), 2); + + Executor executor; + executor.RegisterTable("hits", columnar_file.Path()); + + auto result = executor.Execute( + "SELECT WatchID, ClientIP, COUNT(*) AS c, SUM(IsRefresh), AVG(ResolutionWidth) FROM hits GROUP BY WatchID, " + "ClientIP ORDER BY c DESC LIMIT 2;"); + ASSERT_TRUE(result.has_value()) << result.error().what(); + + EXPECT_EQ(BatchColumnNames(result.value()), + (std::vector{"WatchID", "ClientIP", "c", "SUM(IsRefresh)", "AVG(ResolutionWidth)"})); + EXPECT_EQ(BatchRows(result.value()), (std::vector>{ + {"10", "1", "3", "2", "200"}, + {"11", "1", "2", "1", "500"}, + })); +} + TEST(executor, executes_basic_aggregate_queries) { { const Batch batch = BuildHitsTable("SELECT COUNT(*) FROM hits WHERE AdvEngineID <> 0;"); From 7d10f52890934d20c80dd51a9fe152dfd2f05b55 Mon Sep 17 00:00:00 2001 From: ik4rd Date: Fri, 29 May 2026 18:31:29 +0300 Subject: [PATCH 02/16] fix: drop cache issue fixed --- scripts/dashboard.py | 97 +++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 96 insertions(+), 1 deletion(-) diff --git a/scripts/dashboard.py b/scripts/dashboard.py index 01c4254..678dd52 100644 --- a/scripts/dashboard.py +++ b/scripts/dashboard.py @@ -6,7 +6,11 @@ import csv import datetime as dt import math +import os import pathlib +import platform +import shlex +import shutil import statistics import subprocess import sys @@ -82,6 +86,19 @@ def parse_args() -> argparse.Namespace: choices=("none", "lz4"), help="Compression codec for the CSV -> columnar conversion benchmark.", ) + parser.add_argument( + "--cache-drop-mode", + default="per-query", + choices=("none", "once", "per-query"), + help=( + "Drop the filesystem page cache before benchmarks: never, once before all queries, " + "or before the first run of each query." + ), + ) + parser.add_argument( + "--cache-drop-command", + help="Override the OS-specific cache drop command, for example: 'sudo purge'.", + ) return parser.parse_args() @@ -192,6 +209,8 @@ def collect_conversion_stats( source_csv_path: pathlib.Path, row_group_size: int, compression: str, + drop_cache: bool, + cache_drop_command: str | None, ) -> dict[str, float | int | str] | None: if not schema_path.exists() or not source_csv_path.exists(): return None @@ -227,6 +246,9 @@ def collect_conversion_stats( str(roundtrip_csv), ] + if drop_cache: + drop_file_system_cache(cache_drop_command) + convert_started_at = time.perf_counter() convert_completed = subprocess.run(convert_command, capture_output=True, text=True) convert_elapsed_seconds = time.perf_counter() - convert_started_at @@ -234,6 +256,9 @@ def collect_conversion_stats( message = convert_completed.stderr.strip() or convert_completed.stdout.strip() or "convert failed" raise RuntimeError(message) + if drop_cache: + drop_file_system_cache(cache_drop_command) + roundtrip_started_at = time.perf_counter() roundtrip_completed = subprocess.run(roundtrip_command, capture_output=True, text=True) roundtrip_elapsed_seconds = time.perf_counter() - roundtrip_started_at @@ -265,6 +290,61 @@ def collect_conversion_stats( } +def default_cache_drop_command() -> list[str] | None: + system = platform.system() + if system == "Darwin": + purge = shutil.which("purge") + return [purge] if purge else None + + if system == "Linux": + return ["__linux_drop_caches__"] + + return None + + +def run_cache_drop_command(command: list[str]) -> None: + if not command: + raise RuntimeError("cache drop command is empty") + + if command == ["__linux_drop_caches__"]: + subprocess.run(["sync"], check=True) + drop_caches_path = pathlib.Path("/proc/sys/vm/drop_caches") + if os.geteuid() == 0: + drop_caches_path.write_text("3\n", encoding="utf-8") + return + + try: + completed = subprocess.run( + ["sudo", "-n", "tee", str(drop_caches_path)], + input="3\n", + capture_output=True, + text=True, + ) + except FileNotFoundError as error: + raise RuntimeError("cache drop requires root or sudo on Linux") from error + else: + try: + completed = subprocess.run(command, capture_output=True, text=True) + except FileNotFoundError as error: + raise RuntimeError(f"cache drop command not found: {command[0]}") from error + + if completed.returncode != 0: + stderr = completed.stderr.strip() + stdout = completed.stdout.strip() + message = stderr or stdout or f"exit code {completed.returncode}" + raise RuntimeError(message) + + +def drop_file_system_cache(cache_drop_command: str | None) -> None: + command = shlex.split(cache_drop_command) if cache_drop_command else default_cache_drop_command() + if command is None: + raise RuntimeError( + "cache drop is not supported on this OS; pass --cache-drop-mode none or --cache-drop-command" + ) + + run_cache_drop_command(command) + + def run_query( binary: pathlib.Path, input_path: pathlib.Path, @@ -307,12 +387,17 @@ def collect_rows( count: int, warm_runs: int, table_name: str, + cache_drop_mode: str, + cache_drop_command: str | None, ) -> list[dict[str, str]]: rows: list[dict[str, str]] = [] with tempfile.TemporaryDirectory(prefix="readme_benchmarks_") as temp_dir_name: temp_dir = pathlib.Path(temp_dir_name) + if cache_drop_mode == "once": + drop_file_system_cache(cache_drop_command) + for query_id in range(count): query_path = query_dir / f"query_{query_id}.sql" if not query_path.exists(): @@ -330,6 +415,9 @@ def collect_rows( warm_median_ms = "" output_csv_bytes = "0" + if cache_drop_mode == "per-query": + drop_file_system_cache(cache_drop_command) + try: first_run_value = run_query(binary, input_path, query_path, output_path, table_name) first_run_ms = format_ms(first_run_value) @@ -444,6 +532,7 @@ def render_markdown( rows: list[dict[str, str]], count: int, warm_runs: int, + cache_drop_mode: str, conversion_stats: dict[str, float | int | str] | None, ) -> str: generated_at = dt.datetime.now().strftime("%Y-%m-%d %H:%M:%S") @@ -481,7 +570,8 @@ def render_markdown( lines = [ "## Benchmark Dashboard", "", - f"`generated {generated_at}` · `queries {len(rows)}/{count}` · `warm runs {warm_runs}` · [`csv`]({csv_path})", + f"`generated {generated_at}` · `queries {len(rows)}/{count}` · `warm runs {warm_runs}` · " + f"`cache drop {cache_drop_mode}` · [`csv`]({csv_path})", "", ] @@ -616,6 +706,8 @@ def main() -> int: count=args.count, warm_runs=args.warm_runs, table_name=args.table_name, + cache_drop_mode=args.cache_drop_mode, + cache_drop_command=args.cache_drop_command, ) conversion_stats = collect_conversion_stats( root=root, @@ -624,6 +716,8 @@ def main() -> int: source_csv_path=source_csv_path, row_group_size=args.row_group_size, compression=args.compression, + drop_cache=args.cache_drop_mode != "none", + cache_drop_command=args.cache_drop_command, ) write_csv(csv_path, rows) markdown_block = render_markdown( @@ -631,6 +725,7 @@ def main() -> int: rows, args.count, args.warm_runs, + args.cache_drop_mode, conversion_stats, ) update_readme(readme_path, markdown_block) From 6a859b87649f622650cf94c5fe02be89e369d7e1 Mon Sep 17 00:00:00 2001 From: ik4rd Date: Fri, 29 May 2026 18:31:45 +0300 Subject: [PATCH 03/16] upd: update dashboard --- README.md | 122 +++++++++++------------ benchmarks/results/readme_benchmarks.csv | 86 ++++++++-------- 2 files changed, 104 insertions(+), 104 deletions(-) diff --git a/README.md b/README.md index 7220807..2dfe8d1 100644 --- a/README.md +++ b/README.md @@ -5,7 +5,7 @@ ## Benchmark Dashboard -`generated 2026-05-28 21:28:51` · `queries 43/43` · `warm runs 4` · [`csv`](benchmarks/results/readme_benchmarks.csv) +`generated 2026-05-29 18:28:35` · `queries 43/43` · `warm runs 4` · `cache drop per-query` · [`csv`](benchmarks/results/readme_benchmarks.csv) @@ -16,14 +16,14 @@ - - - - + + + + - - + +
queries ok43 / 43
queries failed0
median warm time220.81 ms
average warm time231.63 ms
p95 warm time476.62 ms
cold/warm delta5.3%
median warm time228.91 ms
average warm time250.97 ms
p95 warm time484.49 ms
cold/warm delta69.2%
total output size20.3 KB
max output size6.0 KB
fastest queryQ06 · 4.12 ms
slowest queryQ28 · 969.54 ms
fastest queryQ00 · 4.32 ms
slowest queryQ28 · 1037.26 ms
@@ -40,9 +40,9 @@ roundtrip csv size743.0 MB compression ratio6.71x columnar / csv14.9% -csv -> columnar5.81s -columnar -> csv9.84s -convert throughput138.1 MB/s +csv -> columnar6.15s +columnar -> csv9.81s +convert throughput130.5 MB/s roundtrip throughput12.2 MB/s @@ -56,60 +56,60 @@ | slot 1 | slot 2 | slot 3 | slot 4 | slot 5 | slot 6 | | ---: | ---: | ---: | ---: | ---: | ---: | -| Q00 🟩 `7.14` | Q01 🟩 `12.85` | Q02 🟩 `21.68` | Q03 🟩 `14.30` | Q04 🟩 `147.02` | Q05 🟩 `128.76` | -| Q06 🟩 `4.12` | Q07 🟩 `11.69` | Q08 🟩 `189.51` | Q09 🟦 `208.53` | Q10 🟥 `267.84` | Q11 🟥 `267.06` | -| Q12 🟦 `225.37` | Q13 🟥 `253.71` | Q14 🟦 `227.55` | Q15 🟩 `74.16` | Q16 🟩 `116.97` | Q17 🟩 `146.00` | -| Q18 🟥 `349.51` | Q19 🟩 `4.39` | Q20 🟥 `252.69` | Q21 🟥 `277.31` | Q22 🟥 `364.71` | Q23 🟦 `240.62` | -| Q24 🟦 `220.81` | Q25 🟦 `219.69` | Q26 🟦 `220.03` | Q27 🟥 `450.51` | Q28 🟥 `969.54` | Q29 🟥 `427.54` | -| Q30 🟦 `235.10` | Q31 🟥 `262.43` | Q32 🟥 `881.20` | Q33 🟥 `349.73` | Q34 🟥 `420.41` | Q35 🟩 `149.57` | -| Q36 🟥 `339.52` | Q37 🟥 `247.46` | Q38 🟩 `73.93` | Q39 🟥 `479.52` | Q40 🟩 `95.10` | Q41 🟩 `34.88` | -| Q42 🟩 `69.69` | | | | | | +| Q00 🟩 `4.32` | Q01 🟩 `11.76` | Q02 🟩 `22.01` | Q03 🟩 `18.75` | Q04 🟩 `149.04` | Q05 🟩 `130.82` | +| Q06 🟩 `4.89` | Q07 🟩 `34.49` | Q08 🟦 `209.66` | Q09 🟦 `210.86` | Q10 🟥 `321.56` | Q11 🟥 `269.94` | +| Q12 🟦 `225.75` | Q13 🟥 `254.04` | Q14 🟦 `229.45` | Q15 🟩 `77.77` | Q16 🟩 `123.55` | Q17 🟩 `155.37` | +| Q18 🟥 `409.06` | Q19 🟩 `4.78` | Q20 🟦 `250.25` | Q21 🟥 `274.39` | Q22 🟥 `379.26` | Q23 🟥 `259.48` | +| Q24 🟦 `244.56` | Q25 🟦 `228.91` | Q26 🟦 `225.43` | Q27 🟥 `484.59` | Q28 🟥 `1037.26` | Q29 🟥 `483.60` | +| Q30 🟦 `247.41` | Q31 🟥 `289.23` | Q32 🟥 `970.94` | Q33 🟥 `435.10` | Q34 🟥 `467.90` | Q35 🟩 `185.10` | +| Q36 🟥 `470.23` | Q37 🟥 `272.08` | Q38 🟩 `72.51` | Q39 🟥 `447.60` | Q40 🟩 `93.99` | Q41 🟩 `34.60` | +| Q42 🟩 `69.50` | | | | | | ### Query Table | Query | Output CSV | First run, ms | Warm avg, ms | Warm median, ms | Warm min, ms | Warm max, ms | Status | | --- | ---: | ---: | ---: | ---: | ---: | ---: | --- | -| [Q00](benchmarks/queries/query_0.sql) | 7 B | 372.47 | 7.12 | 7.14 | 5.32 | 8.89 | ok | -| [Q01](benchmarks/queries/query_1.sql) | 6 B | 16.18 | 13.17 | 12.85 | 11.37 | 15.61 | ok | -| [Q02](benchmarks/queries/query_2.sql) | 18 B | 28.44 | 21.85 | 21.68 | 21.58 | 22.45 | ok | -| [Q03](benchmarks/queries/query_3.sql) | 20 B | 14.56 | 14.35 | 14.30 | 14.24 | 14.55 | ok | -| [Q04](benchmarks/queries/query_4.sql) | 6 B | 151.94 | 148.55 | 147.02 | 146.46 | 153.70 | ok | -| [Q05](benchmarks/queries/query_5.sql) | 6 B | 131.23 | 128.78 | 128.76 | 128.60 | 129.00 | ok | -| [Q06](benchmarks/queries/query_6.sql) | 22 B | 4.63 | 4.11 | 4.12 | 4.00 | 4.22 | ok | -| [Q07](benchmarks/queries/query_7.sql) | 31 B | 11.54 | 11.68 | 11.69 | 11.61 | 11.71 | ok | -| [Q08](benchmarks/queries/query_8.sql) | 81 B | 189.62 | 189.70 | 189.51 | 189.29 | 190.49 | ok | -| [Q09](benchmarks/queries/query_9.sql) | 237 B | 209.27 | 209.04 | 208.53 | 207.81 | 211.30 | ok | -| [Q10](benchmarks/queries/query_10.sql) | 102 B | 273.63 | 268.25 | 267.84 | 264.68 | 272.65 | ok | -| [Q11](benchmarks/queries/query_11.sql) | 116 B | 267.74 | 267.06 | 267.06 | 266.30 | 267.81 | ok | -| [Q12](benchmarks/queries/query_12.sql) | 387 B | 225.38 | 225.50 | 225.37 | 225.15 | 226.12 | ok | -| [Q13](benchmarks/queries/query_13.sql) | 350 B | 255.52 | 253.82 | 253.71 | 253.17 | 254.72 | ok | -| [Q14](benchmarks/queries/query_14.sql) | 400 B | 227.71 | 227.58 | 227.55 | 227.02 | 228.18 | ok | -| [Q15](benchmarks/queries/query_15.sql) | 236 B | 73.92 | 74.08 | 74.16 | 73.23 | 74.78 | ok | -| [Q16](benchmarks/queries/query_16.sql) | 244 B | 116.27 | 117.15 | 116.97 | 116.74 | 117.90 | ok | -| [Q17](benchmarks/queries/query_17.sql) | 414 B | 146.45 | 145.98 | 146.00 | 145.32 | 146.60 | ok | -| [Q18](benchmarks/queries/query_18.sql) | 264 B | 371.65 | 354.57 | 349.51 | 345.73 | 373.53 | ok | -| [Q19](benchmarks/queries/query_19.sql) | 0 B | 5.11 | 4.40 | 4.39 | 4.35 | 4.45 | ok | -| [Q20](benchmarks/queries/query_20.sql) | 3 B | 253.57 | 252.47 | 252.69 | 251.74 | 252.77 | ok | -| [Q21](benchmarks/queries/query_21.sql) | 162 B | 275.26 | 277.07 | 277.31 | 274.21 | 279.46 | ok | -| [Q22](benchmarks/queries/query_22.sql) | 6.0 KB | 364.65 | 364.26 | 364.71 | 362.10 | 365.51 | ok | -| [Q23](benchmarks/queries/query_23.sql) | 2.6 KB | 247.58 | 240.46 | 240.62 | 239.89 | 240.72 | ok | -| [Q24](benchmarks/queries/query_24.sql) | 672 B | 221.33 | 220.77 | 220.81 | 220.51 | 220.95 | ok | -| [Q25](benchmarks/queries/query_25.sql) | 382 B | 219.30 | 219.65 | 219.69 | 218.74 | 220.49 | ok | -| [Q26](benchmarks/queries/query_26.sql) | 658 B | 221.11 | 220.32 | 220.03 | 219.98 | 221.26 | ok | -| [Q27](benchmarks/queries/query_27.sql) | 26 B | 448.24 | 450.32 | 450.51 | 448.06 | 452.19 | ok | -| [Q28](benchmarks/queries/query_28.sql) | 556 B | 972.11 | 969.67 | 969.54 | 964.82 | 974.79 | ok | -| [Q29](benchmarks/queries/query_29.sql) | 990 B | 436.39 | 427.32 | 427.54 | 426.26 | 427.95 | ok | -| [Q30](benchmarks/queries/query_30.sql) | 244 B | 237.16 | 235.05 | 235.10 | 234.09 | 235.90 | ok | -| [Q31](benchmarks/queries/query_31.sql) | 398 B | 264.83 | 262.59 | 262.43 | 261.71 | 263.79 | ok | -| [Q32](benchmarks/queries/query_32.sql) | 376 B | 1089.10 | 866.39 | 881.20 | 808.51 | 894.64 | ok | -| [Q33](benchmarks/queries/query_33.sql) | 464 B | 354.03 | 349.81 | 349.73 | 349.37 | 350.43 | ok | -| [Q34](benchmarks/queries/query_34.sql) | 484 B | 369.46 | 435.58 | 420.41 | 370.29 | 531.22 | ok | -| [Q35](benchmarks/queries/query_35.sql) | 493 B | 151.95 | 149.11 | 149.57 | 142.89 | 154.43 | ok | -| [Q36](benchmarks/queries/query_36.sql) | 569 B | 331.43 | 345.67 | 339.52 | 333.21 | 370.41 | ok | -| [Q37](benchmarks/queries/query_37.sql) | 550 B | 249.16 | 247.76 | 247.46 | 246.64 | 249.47 | ok | -| [Q38](benchmarks/queries/query_38.sql) | 480 B | 79.44 | 74.20 | 73.93 | 73.32 | 75.60 | ok | -| [Q39](benchmarks/queries/query_39.sql) | 835 B | 400.94 | 541.20 | 479.52 | 391.72 | 814.03 | ok | -| [Q40](benchmarks/queries/query_40.sql) | 356 B | 103.53 | 95.10 | 95.10 | 94.63 | 95.58 | ok | -| [Q41](benchmarks/queries/query_41.sql) | 132 B | 39.71 | 34.90 | 34.88 | 34.50 | 35.36 | ok | -| [Q42](benchmarks/queries/query_42.sql) | 240 B | 68.37 | 69.46 | 69.69 | 68.04 | 70.42 | ok | +| [Q00](benchmarks/queries/query_0.sql) | 7 B | 113.25 | 4.35 | 4.32 | 4.12 | 4.63 | ok | +| [Q01](benchmarks/queries/query_1.sql) | 6 B | 137.27 | 21.96 | 11.76 | 11.52 | 52.82 | ok | +| [Q02](benchmarks/queries/query_2.sql) | 18 B | 142.08 | 22.02 | 22.01 | 21.74 | 22.31 | ok | +| [Q03](benchmarks/queries/query_3.sql) | 20 B | 123.64 | 18.46 | 18.75 | 16.21 | 20.13 | ok | +| [Q04](benchmarks/queries/query_4.sql) | 6 B | 234.96 | 150.05 | 149.04 | 148.08 | 154.04 | ok | +| [Q05](benchmarks/queries/query_5.sql) | 6 B | 240.36 | 130.98 | 130.82 | 129.98 | 132.32 | ok | +| [Q06](benchmarks/queries/query_6.sql) | 22 B | 85.34 | 5.07 | 4.89 | 4.46 | 6.01 | ok | +| [Q07](benchmarks/queries/query_7.sql) | 31 B | 239.83 | 31.86 | 34.49 | 14.17 | 44.29 | ok | +| [Q08](benchmarks/queries/query_8.sql) | 81 B | 580.94 | 209.67 | 209.66 | 195.59 | 223.77 | ok | +| [Q09](benchmarks/queries/query_9.sql) | 237 B | 375.45 | 212.19 | 210.86 | 210.84 | 216.20 | ok | +| [Q10](benchmarks/queries/query_10.sql) | 102 B | 398.40 | 330.03 | 321.56 | 268.15 | 408.83 | ok | +| [Q11](benchmarks/queries/query_11.sql) | 116 B | 406.75 | 270.12 | 269.94 | 268.30 | 272.31 | ok | +| [Q12](benchmarks/queries/query_12.sql) | 387 B | 358.79 | 226.49 | 225.75 | 224.20 | 230.24 | ok | +| [Q13](benchmarks/queries/query_13.sql) | 350 B | 418.79 | 254.61 | 254.04 | 252.88 | 257.49 | ok | +| [Q14](benchmarks/queries/query_14.sql) | 400 B | 432.13 | 229.34 | 229.45 | 226.48 | 231.97 | ok | +| [Q15](benchmarks/queries/query_15.sql) | 236 B | 202.02 | 77.58 | 77.77 | 75.24 | 79.54 | ok | +| [Q16](benchmarks/queries/query_16.sql) | 244 B | 328.95 | 123.58 | 123.55 | 122.32 | 124.89 | ok | +| [Q17](benchmarks/queries/query_17.sql) | 414 B | 314.07 | 156.98 | 155.37 | 154.82 | 162.36 | ok | +| [Q18](benchmarks/queries/query_18.sql) | 264 B | 593.68 | 397.69 | 409.06 | 359.36 | 413.31 | ok | +| [Q19](benchmarks/queries/query_19.sql) | 0 B | 136.87 | 4.81 | 4.78 | 4.61 | 5.08 | ok | +| [Q20](benchmarks/queries/query_20.sql) | 3 B | 429.77 | 250.00 | 250.25 | 248.39 | 251.10 | ok | +| [Q21](benchmarks/queries/query_21.sql) | 162 B | 501.89 | 275.00 | 274.39 | 272.76 | 278.44 | ok | +| [Q22](benchmarks/queries/query_22.sql) | 6.0 KB | 613.76 | 388.76 | 379.26 | 374.02 | 422.49 | ok | +| [Q23](benchmarks/queries/query_23.sql) | 2.6 KB | 535.96 | 256.21 | 259.48 | 244.46 | 261.44 | ok | +| [Q24](benchmarks/queries/query_24.sql) | 672 B | 481.76 | 244.80 | 244.56 | 238.24 | 251.85 | ok | +| [Q25](benchmarks/queries/query_25.sql) | 382 B | 323.60 | 237.19 | 228.91 | 221.17 | 269.75 | ok | +| [Q26](benchmarks/queries/query_26.sql) | 658 B | 524.01 | 225.42 | 225.43 | 223.30 | 227.52 | ok | +| [Q27](benchmarks/queries/query_27.sql) | 26 B | 630.87 | 538.84 | 484.59 | 473.97 | 712.22 | ok | +| [Q28](benchmarks/queries/query_28.sql) | 556 B | 1281.69 | 1032.44 | 1037.26 | 974.01 | 1081.23 | ok | +| [Q29](benchmarks/queries/query_29.sql) | 990 B | 688.07 | 492.24 | 483.60 | 456.93 | 544.81 | ok | +| [Q30](benchmarks/queries/query_30.sql) | 244 B | 417.32 | 247.59 | 247.41 | 242.87 | 252.69 | ok | +| [Q31](benchmarks/queries/query_31.sql) | 398 B | 537.23 | 288.86 | 289.23 | 274.97 | 302.04 | ok | +| [Q32](benchmarks/queries/query_32.sql) | 376 B | 1146.97 | 972.70 | 970.94 | 952.74 | 996.17 | ok | +| [Q33](benchmarks/queries/query_33.sql) | 464 B | 565.29 | 445.48 | 435.10 | 373.51 | 538.19 | ok | +| [Q34](benchmarks/queries/query_34.sql) | 484 B | 601.70 | 470.54 | 467.90 | 440.92 | 505.46 | ok | +| [Q35](benchmarks/queries/query_35.sql) | 493 B | 377.26 | 189.25 | 185.10 | 172.52 | 214.30 | ok | +| [Q36](benchmarks/queries/query_36.sql) | 569 B | 505.12 | 595.08 | 470.23 | 367.62 | 1072.25 | ok | +| [Q37](benchmarks/queries/query_37.sql) | 550 B | 652.50 | 269.23 | 272.08 | 246.11 | 286.66 | ok | +| [Q38](benchmarks/queries/query_38.sql) | 480 B | 221.58 | 75.18 | 72.51 | 71.88 | 83.83 | ok | +| [Q39](benchmarks/queries/query_39.sql) | 835 B | 620.65 | 435.00 | 447.60 | 393.28 | 451.52 | ok | +| [Q40](benchmarks/queries/query_40.sql) | 356 B | 275.34 | 93.89 | 93.99 | 93.48 | 94.10 | ok | +| [Q41](benchmarks/queries/query_41.sql) | 132 B | 246.34 | 34.89 | 34.60 | 33.65 | 36.68 | ok | +| [Q42](benchmarks/queries/query_42.sql) | 240 B | 217.42 | 69.30 | 69.50 | 67.93 | 70.24 | ok | diff --git a/benchmarks/results/readme_benchmarks.csv b/benchmarks/results/readme_benchmarks.csv index 673aaa1..8e9cec8 100644 --- a/benchmarks/results/readme_benchmarks.csv +++ b/benchmarks/results/readme_benchmarks.csv @@ -1,44 +1,44 @@ query_id,query_file,sql,first_run_ms,warm_avg_ms,warm_median_ms,warm_min_ms,warm_max_ms,output_csv_bytes,status,error -0,benchmarks/queries/query_0.sql,SELECT COUNT(*) FROM hits;,372.47,7.12,7.14,5.32,8.89,7,ok, -1,benchmarks/queries/query_1.sql,SELECT COUNT(*) FROM hits WHERE AdvEngineID <> 0;,16.18,13.17,12.85,11.37,15.61,6,ok, -2,benchmarks/queries/query_2.sql,"SELECT SUM(AdvEngineID), COUNT(*), AVG(ResolutionWidth) FROM hits;",28.44,21.85,21.68,21.58,22.45,18,ok, -3,benchmarks/queries/query_3.sql,SELECT AVG(UserID) FROM hits;,14.56,14.35,14.30,14.24,14.55,20,ok, -4,benchmarks/queries/query_4.sql,SELECT COUNT(DISTINCT UserID) FROM hits;,151.94,148.55,147.02,146.46,153.70,6,ok, -5,benchmarks/queries/query_5.sql,SELECT COUNT(DISTINCT SearchPhrase) FROM hits;,131.23,128.78,128.76,128.60,129.00,6,ok, -6,benchmarks/queries/query_6.sql,"SELECT MIN(EventDate), MAX(EventDate) FROM hits;",4.63,4.11,4.12,4.00,4.22,22,ok, -7,benchmarks/queries/query_7.sql,"SELECT AdvEngineID, COUNT(*) FROM hits WHERE AdvEngineID <> 0 GROUP BY AdvEngineID ORDER BY COUNT(*) DESC;",11.54,11.68,11.69,11.61,11.71,31,ok, -8,benchmarks/queries/query_8.sql,"SELECT RegionID, COUNT(DISTINCT UserID) AS u FROM hits GROUP BY RegionID ORDER BY u DESC LIMIT 10;",189.62,189.70,189.51,189.29,190.49,81,ok, -9,benchmarks/queries/query_9.sql,"SELECT RegionID, SUM(AdvEngineID), COUNT(*) AS c, AVG(ResolutionWidth), COUNT(DISTINCT UserID) FROM hits GROUP BY RegionID ORDER BY c DESC LIMIT 10;",209.27,209.04,208.53,207.81,211.30,237,ok, -10,benchmarks/queries/query_10.sql,"SELECT MobilePhoneModel, COUNT(DISTINCT UserID) AS u FROM hits WHERE MobilePhoneModel <> '' GROUP BY MobilePhoneModel ORDER BY u DESC LIMIT 10;",273.63,268.25,267.84,264.68,272.65,102,ok, -11,benchmarks/queries/query_11.sql,"SELECT MobilePhone, MobilePhoneModel, COUNT(DISTINCT UserID) AS u FROM hits WHERE MobilePhoneModel <> '' GROUP BY MobilePhone, MobilePhoneModel ORDER BY u DESC LIMIT 10;",267.74,267.06,267.06,266.30,267.81,116,ok, -12,benchmarks/queries/query_12.sql,"SELECT SearchPhrase, COUNT(*) AS c FROM hits WHERE SearchPhrase <> '' GROUP BY SearchPhrase ORDER BY c DESC LIMIT 10;",225.38,225.50,225.37,225.15,226.12,387,ok, -13,benchmarks/queries/query_13.sql,"SELECT SearchPhrase, COUNT(DISTINCT UserID) AS u FROM hits WHERE SearchPhrase <> '' GROUP BY SearchPhrase ORDER BY u DESC LIMIT 10;",255.52,253.82,253.71,253.17,254.72,350,ok, -14,benchmarks/queries/query_14.sql,"SELECT SearchEngineID, SearchPhrase, COUNT(*) AS c FROM hits WHERE SearchPhrase <> '' GROUP BY SearchEngineID, SearchPhrase ORDER BY c DESC LIMIT 10;",227.71,227.58,227.55,227.02,228.18,400,ok, -15,benchmarks/queries/query_15.sql,"SELECT UserID, COUNT(*) FROM hits GROUP BY UserID ORDER BY COUNT(*) DESC LIMIT 10;",73.92,74.08,74.16,73.23,74.78,236,ok, -16,benchmarks/queries/query_16.sql,"SELECT UserID, SearchPhrase, COUNT(*) FROM hits GROUP BY UserID, SearchPhrase ORDER BY COUNT(*) DESC LIMIT 10;",116.27,117.15,116.97,116.74,117.90,244,ok, -17,benchmarks/queries/query_17.sql,"SELECT UserID, SearchPhrase, COUNT(*) FROM hits GROUP BY UserID, SearchPhrase LIMIT 10;",146.45,145.98,146.00,145.32,146.60,414,ok, -18,benchmarks/queries/query_18.sql,"SELECT UserID, extract(minute FROM EventTime) AS m, SearchPhrase, COUNT(*) FROM hits GROUP BY UserID, m, SearchPhrase ORDER BY COUNT(*) DESC LIMIT 10;",371.65,354.57,349.51,345.73,373.53,264,ok, -19,benchmarks/queries/query_19.sql,SELECT UserID FROM hits WHERE UserID = 435090932899640449;,5.11,4.40,4.39,4.35,4.45,0,ok, -20,benchmarks/queries/query_20.sql,SELECT COUNT(*) FROM hits WHERE URL LIKE '%google%';,253.57,252.47,252.69,251.74,252.77,3,ok, -21,benchmarks/queries/query_21.sql,"SELECT SearchPhrase, MIN(URL), COUNT(*) AS c FROM hits WHERE URL LIKE '%google%' AND SearchPhrase <> '' GROUP BY SearchPhrase ORDER BY c DESC LIMIT 10;",275.26,277.07,277.31,274.21,279.46,162,ok, -22,benchmarks/queries/query_22.sql,"SELECT SearchPhrase, MIN(URL), MIN(Title), COUNT(*) AS c, COUNT(DISTINCT UserID) FROM hits WHERE Title LIKE '%Google%' AND URL NOT LIKE '%.google.%' AND SearchPhrase <> '' GROUP BY SearchPhrase ORDER BY c DESC LIMIT 10;",364.65,364.26,364.71,362.10,365.51,6188,ok, -23,benchmarks/queries/query_23.sql,SELECT * FROM hits WHERE URL LIKE '%google%' ORDER BY EventTime LIMIT 10;,247.58,240.46,240.62,239.89,240.72,2633,ok, -24,benchmarks/queries/query_24.sql,SELECT SearchPhrase FROM hits WHERE SearchPhrase <> '' ORDER BY EventTime LIMIT 10;,221.33,220.77,220.81,220.51,220.95,672,ok, -25,benchmarks/queries/query_25.sql,SELECT SearchPhrase FROM hits WHERE SearchPhrase <> '' ORDER BY SearchPhrase LIMIT 10;,219.30,219.65,219.69,218.74,220.49,382,ok, -26,benchmarks/queries/query_26.sql,"SELECT SearchPhrase FROM hits WHERE SearchPhrase <> '' ORDER BY EventTime, SearchPhrase LIMIT 10;",221.11,220.32,220.03,219.98,221.26,658,ok, -27,benchmarks/queries/query_27.sql,"SELECT CounterID, AVG(STRLEN(URL)) AS l, COUNT(*) AS c FROM hits WHERE URL <> '' GROUP BY CounterID HAVING COUNT(*) > 100000 ORDER BY l DESC LIMIT 25;",448.24,450.32,450.51,448.06,452.19,26,ok, -28,benchmarks/queries/query_28.sql,"SELECT REGEXP_REPLACE(Referer, '^https?://(?:www\.)?([^/]+)/.*$', '\1') AS k, AVG(STRLEN(Referer)) AS l, COUNT(*) AS c, MIN(Referer) FROM hits WHERE Referer <> '' GROUP BY k HAVING COUNT(*) > 100000 ORDER BY l DESC LIMIT 25;",972.11,969.67,969.54,964.82,974.79,556,ok, -29,benchmarks/queries/query_29.sql,"SELECT SUM(ResolutionWidth), SUM(ResolutionWidth + 1), SUM(ResolutionWidth + 2), SUM(ResolutionWidth + 3), SUM(ResolutionWidth + 4), SUM(ResolutionWidth + 5), SUM(ResolutionWidth + 6), SUM(ResolutionWidth + 7), SUM(ResolutionWidth + 8), SUM(ResolutionWidth + 9), SUM(ResolutionWidth + 10), SUM(ResolutionWidth + 11), SUM(ResolutionWidth + 12), SUM(ResolutionWidth + 13), SUM(ResolutionWidth + 14), SUM(ResolutionWidth + 15), SUM(ResolutionWidth + 16), SUM(ResolutionWidth + 17), SUM(ResolutionWidth + 18), SUM(ResolutionWidth + 19), SUM(ResolutionWidth + 20), SUM(ResolutionWidth + 21), SUM(ResolutionWidth + 22), SUM(ResolutionWidth + 23), SUM(ResolutionWidth + 24), SUM(ResolutionWidth + 25), SUM(ResolutionWidth + 26), SUM(ResolutionWidth + 27), SUM(ResolutionWidth + 28), SUM(ResolutionWidth + 29), SUM(ResolutionWidth + 30), SUM(ResolutionWidth + 31), SUM(ResolutionWidth + 32), SUM(ResolutionWidth + 33), SUM(ResolutionWidth + 34), SUM(ResolutionWidth + 35), SUM(ResolutionWidth + 36), SUM(ResolutionWidth + 37), SUM(ResolutionWidth + 38), SUM(ResolutionWidth + 39), SUM(ResolutionWidth + 40), SUM(ResolutionWidth + 41), SUM(ResolutionWidth + 42), SUM(ResolutionWidth + 43), SUM(ResolutionWidth + 44), SUM(ResolutionWidth + 45), SUM(ResolutionWidth + 46), SUM(ResolutionWidth + 47), SUM(ResolutionWidth + 48), SUM(ResolutionWidth + 49), SUM(ResolutionWidth + 50), SUM(ResolutionWidth + 51), SUM(ResolutionWidth + 52), SUM(ResolutionWidth + 53), SUM(ResolutionWidth + 54), SUM(ResolutionWidth + 55), SUM(ResolutionWidth + 56), SUM(ResolutionWidth + 57), SUM(ResolutionWidth + 58), SUM(ResolutionWidth + 59), SUM(ResolutionWidth + 60), SUM(ResolutionWidth + 61), SUM(ResolutionWidth + 62), SUM(ResolutionWidth + 63), SUM(ResolutionWidth + 64), SUM(ResolutionWidth + 65), SUM(ResolutionWidth + 66), SUM(ResolutionWidth + 67), SUM(ResolutionWidth + 68), SUM(ResolutionWidth + 69), SUM(ResolutionWidth + 70), SUM(ResolutionWidth + 71), SUM(ResolutionWidth + 72), SUM(ResolutionWidth + 73), SUM(ResolutionWidth + 74), SUM(ResolutionWidth + 75), SUM(ResolutionWidth + 76), SUM(ResolutionWidth + 77), SUM(ResolutionWidth + 78), SUM(ResolutionWidth + 79), SUM(ResolutionWidth + 80), SUM(ResolutionWidth + 81), SUM(ResolutionWidth + 82), SUM(ResolutionWidth + 83), SUM(ResolutionWidth + 84), SUM(ResolutionWidth + 85), SUM(ResolutionWidth + 86), SUM(ResolutionWidth + 87), SUM(ResolutionWidth + 88), SUM(ResolutionWidth + 89) FROM hits;",436.39,427.32,427.54,426.26,427.95,990,ok, -30,benchmarks/queries/query_30.sql,"SELECT SearchEngineID, ClientIP, COUNT(*) AS c, SUM(IsRefresh), AVG(ResolutionWidth) FROM hits WHERE SearchPhrase <> '' GROUP BY SearchEngineID, ClientIP ORDER BY c DESC LIMIT 10;",237.16,235.05,235.10,234.09,235.90,244,ok, -31,benchmarks/queries/query_31.sql,"SELECT WatchID, ClientIP, COUNT(*) AS c, SUM(IsRefresh), AVG(ResolutionWidth) FROM hits WHERE SearchPhrase <> '' GROUP BY WatchID, ClientIP ORDER BY c DESC LIMIT 10;",264.83,262.59,262.43,261.71,263.79,398,ok, -32,benchmarks/queries/query_32.sql,"SELECT WatchID, ClientIP, COUNT(*) AS c, SUM(IsRefresh), AVG(ResolutionWidth) FROM hits GROUP BY WatchID, ClientIP ORDER BY c DESC LIMIT 10;",1089.10,866.39,881.20,808.51,894.64,376,ok, -33,benchmarks/queries/query_33.sql,"SELECT URL, COUNT(*) AS c FROM hits GROUP BY URL ORDER BY c DESC LIMIT 10;",354.03,349.81,349.73,349.37,350.43,464,ok, -34,benchmarks/queries/query_34.sql,"SELECT 1, URL, COUNT(*) AS c FROM hits GROUP BY 1, URL ORDER BY c DESC LIMIT 10;",369.46,435.58,420.41,370.29,531.22,484,ok, -35,benchmarks/queries/query_35.sql,"SELECT ClientIP, ClientIP - 1, ClientIP - 2, ClientIP - 3, COUNT(*) AS c FROM hits GROUP BY ClientIP, ClientIP - 1, ClientIP - 2, ClientIP - 3 ORDER BY c DESC LIMIT 10;",151.95,149.11,149.57,142.89,154.43,493,ok, -36,benchmarks/queries/query_36.sql,"SELECT URL, COUNT(*) AS PageViews FROM hits WHERE CounterID = 62 AND EventDate >= '2013-07-01' AND EventDate <= '2013-07-31' AND DontCountHits = 0 AND IsRefresh = 0 AND URL <> '' GROUP BY URL ORDER BY PageViews DESC LIMIT 10;",331.43,345.67,339.52,333.21,370.41,569,ok, -37,benchmarks/queries/query_37.sql,"SELECT Title, COUNT(*) AS PageViews FROM hits WHERE CounterID = 62 AND EventDate >= '2013-07-01' AND EventDate <= '2013-07-31' AND DontCountHits = 0 AND IsRefresh = 0 AND Title <> '' GROUP BY Title ORDER BY PageViews DESC LIMIT 10;",249.16,247.76,247.46,246.64,249.47,550,ok, -38,benchmarks/queries/query_38.sql,"SELECT URL, COUNT(*) AS PageViews FROM hits WHERE CounterID = 62 AND EventDate >= '2013-07-01' AND EventDate <= '2013-07-31' AND IsRefresh = 0 AND IsLink <> 0 AND IsDownload = 0 GROUP BY URL ORDER BY PageViews DESC LIMIT 10 OFFSET 1000;",79.44,74.20,73.93,73.32,75.60,480,ok, -39,benchmarks/queries/query_39.sql,"SELECT TraficSourceID, SearchEngineID, AdvEngineID, CASE WHEN (SearchEngineID = 0 AND AdvEngineID = 0) THEN Referer ELSE '' END AS Src, URL AS Dst, COUNT(*) AS PageViews FROM hits WHERE CounterID = 62 AND EventDate >= '2013-07-01' AND EventDate <= '2013-07-31' AND IsRefresh = 0 GROUP BY TraficSourceID, SearchEngineID, AdvEngineID, Src, Dst ORDER BY PageViews DESC LIMIT 10 OFFSET 1000;",400.94,541.20,479.52,391.72,814.03,835,ok, -40,benchmarks/queries/query_40.sql,"SELECT URLHash, EventDate, COUNT(*) AS PageViews FROM hits WHERE CounterID = 62 AND EventDate >= '2013-07-01' AND EventDate <= '2013-07-31' AND IsRefresh = 0 AND TraficSourceID IN (-1, 6) AND RefererHash = 3594120000172545465 GROUP BY URLHash, EventDate ORDER BY PageViews DESC LIMIT 10 OFFSET 100;",103.53,95.10,95.10,94.63,95.58,356,ok, -41,benchmarks/queries/query_41.sql,"SELECT WindowClientWidth, WindowClientHeight, COUNT(*) AS PageViews FROM hits WHERE CounterID = 62 AND EventDate >= '2013-07-01' AND EventDate <= '2013-07-31' AND IsRefresh = 0 AND DontCountHits = 0 AND URLHash = 2868770270353813622 GROUP BY WindowClientWidth, WindowClientHeight ORDER BY PageViews DESC LIMIT 10 OFFSET 10000;",39.71,34.90,34.88,34.50,35.36,132,ok, -42,benchmarks/queries/query_42.sql,"SELECT DATE_TRUNC('minute', EventTime) AS M, COUNT(*) AS PageViews FROM hits WHERE CounterID = 62 AND EventDate >= '2013-07-14' AND EventDate <= '2013-07-15' AND IsRefresh = 0 AND DontCountHits = 0 GROUP BY DATE_TRUNC('minute', EventTime) ORDER BY DATE_TRUNC('minute', EventTime) LIMIT 10 OFFSET 1000;",68.37,69.46,69.69,68.04,70.42,240,ok, +0,benchmarks/queries/query_0.sql,SELECT COUNT(*) FROM hits;,113.25,4.35,4.32,4.12,4.63,7,ok, +1,benchmarks/queries/query_1.sql,SELECT COUNT(*) FROM hits WHERE AdvEngineID <> 0;,137.27,21.96,11.76,11.52,52.82,6,ok, +2,benchmarks/queries/query_2.sql,"SELECT SUM(AdvEngineID), COUNT(*), AVG(ResolutionWidth) FROM hits;",142.08,22.02,22.01,21.74,22.31,18,ok, +3,benchmarks/queries/query_3.sql,SELECT AVG(UserID) FROM hits;,123.64,18.46,18.75,16.21,20.13,20,ok, +4,benchmarks/queries/query_4.sql,SELECT COUNT(DISTINCT UserID) FROM hits;,234.96,150.05,149.04,148.08,154.04,6,ok, +5,benchmarks/queries/query_5.sql,SELECT COUNT(DISTINCT SearchPhrase) FROM hits;,240.36,130.98,130.82,129.98,132.32,6,ok, +6,benchmarks/queries/query_6.sql,"SELECT MIN(EventDate), MAX(EventDate) FROM hits;",85.34,5.07,4.89,4.46,6.01,22,ok, +7,benchmarks/queries/query_7.sql,"SELECT AdvEngineID, COUNT(*) FROM hits WHERE AdvEngineID <> 0 GROUP BY AdvEngineID ORDER BY COUNT(*) DESC;",239.83,31.86,34.49,14.17,44.29,31,ok, +8,benchmarks/queries/query_8.sql,"SELECT RegionID, COUNT(DISTINCT UserID) AS u FROM hits GROUP BY RegionID ORDER BY u DESC LIMIT 10;",580.94,209.67,209.66,195.59,223.77,81,ok, +9,benchmarks/queries/query_9.sql,"SELECT RegionID, SUM(AdvEngineID), COUNT(*) AS c, AVG(ResolutionWidth), COUNT(DISTINCT UserID) FROM hits GROUP BY RegionID ORDER BY c DESC LIMIT 10;",375.45,212.19,210.86,210.84,216.20,237,ok, +10,benchmarks/queries/query_10.sql,"SELECT MobilePhoneModel, COUNT(DISTINCT UserID) AS u FROM hits WHERE MobilePhoneModel <> '' GROUP BY MobilePhoneModel ORDER BY u DESC LIMIT 10;",398.40,330.03,321.56,268.15,408.83,102,ok, +11,benchmarks/queries/query_11.sql,"SELECT MobilePhone, MobilePhoneModel, COUNT(DISTINCT UserID) AS u FROM hits WHERE MobilePhoneModel <> '' GROUP BY MobilePhone, MobilePhoneModel ORDER BY u DESC LIMIT 10;",406.75,270.12,269.94,268.30,272.31,116,ok, +12,benchmarks/queries/query_12.sql,"SELECT SearchPhrase, COUNT(*) AS c FROM hits WHERE SearchPhrase <> '' GROUP BY SearchPhrase ORDER BY c DESC LIMIT 10;",358.79,226.49,225.75,224.20,230.24,387,ok, +13,benchmarks/queries/query_13.sql,"SELECT SearchPhrase, COUNT(DISTINCT UserID) AS u FROM hits WHERE SearchPhrase <> '' GROUP BY SearchPhrase ORDER BY u DESC LIMIT 10;",418.79,254.61,254.04,252.88,257.49,350,ok, +14,benchmarks/queries/query_14.sql,"SELECT SearchEngineID, SearchPhrase, COUNT(*) AS c FROM hits WHERE SearchPhrase <> '' GROUP BY SearchEngineID, SearchPhrase ORDER BY c DESC LIMIT 10;",432.13,229.34,229.45,226.48,231.97,400,ok, +15,benchmarks/queries/query_15.sql,"SELECT UserID, COUNT(*) FROM hits GROUP BY UserID ORDER BY COUNT(*) DESC LIMIT 10;",202.02,77.58,77.77,75.24,79.54,236,ok, +16,benchmarks/queries/query_16.sql,"SELECT UserID, SearchPhrase, COUNT(*) FROM hits GROUP BY UserID, SearchPhrase ORDER BY COUNT(*) DESC LIMIT 10;",328.95,123.58,123.55,122.32,124.89,244,ok, +17,benchmarks/queries/query_17.sql,"SELECT UserID, SearchPhrase, COUNT(*) FROM hits GROUP BY UserID, SearchPhrase LIMIT 10;",314.07,156.98,155.37,154.82,162.36,414,ok, +18,benchmarks/queries/query_18.sql,"SELECT UserID, extract(minute FROM EventTime) AS m, SearchPhrase, COUNT(*) FROM hits GROUP BY UserID, m, SearchPhrase ORDER BY COUNT(*) DESC LIMIT 10;",593.68,397.69,409.06,359.36,413.31,264,ok, +19,benchmarks/queries/query_19.sql,SELECT UserID FROM hits WHERE UserID = 435090932899640449;,136.87,4.81,4.78,4.61,5.08,0,ok, +20,benchmarks/queries/query_20.sql,SELECT COUNT(*) FROM hits WHERE URL LIKE '%google%';,429.77,250.00,250.25,248.39,251.10,3,ok, +21,benchmarks/queries/query_21.sql,"SELECT SearchPhrase, MIN(URL), COUNT(*) AS c FROM hits WHERE URL LIKE '%google%' AND SearchPhrase <> '' GROUP BY SearchPhrase ORDER BY c DESC LIMIT 10;",501.89,275.00,274.39,272.76,278.44,162,ok, +22,benchmarks/queries/query_22.sql,"SELECT SearchPhrase, MIN(URL), MIN(Title), COUNT(*) AS c, COUNT(DISTINCT UserID) FROM hits WHERE Title LIKE '%Google%' AND URL NOT LIKE '%.google.%' AND SearchPhrase <> '' GROUP BY SearchPhrase ORDER BY c DESC LIMIT 10;",613.76,388.76,379.26,374.02,422.49,6188,ok, +23,benchmarks/queries/query_23.sql,SELECT * FROM hits WHERE URL LIKE '%google%' ORDER BY EventTime LIMIT 10;,535.96,256.21,259.48,244.46,261.44,2633,ok, +24,benchmarks/queries/query_24.sql,SELECT SearchPhrase FROM hits WHERE SearchPhrase <> '' ORDER BY EventTime LIMIT 10;,481.76,244.80,244.56,238.24,251.85,672,ok, +25,benchmarks/queries/query_25.sql,SELECT SearchPhrase FROM hits WHERE SearchPhrase <> '' ORDER BY SearchPhrase LIMIT 10;,323.60,237.19,228.91,221.17,269.75,382,ok, +26,benchmarks/queries/query_26.sql,"SELECT SearchPhrase FROM hits WHERE SearchPhrase <> '' ORDER BY EventTime, SearchPhrase LIMIT 10;",524.01,225.42,225.43,223.30,227.52,658,ok, +27,benchmarks/queries/query_27.sql,"SELECT CounterID, AVG(STRLEN(URL)) AS l, COUNT(*) AS c FROM hits WHERE URL <> '' GROUP BY CounterID HAVING COUNT(*) > 100000 ORDER BY l DESC LIMIT 25;",630.87,538.84,484.59,473.97,712.22,26,ok, +28,benchmarks/queries/query_28.sql,"SELECT REGEXP_REPLACE(Referer, '^https?://(?:www\.)?([^/]+)/.*$', '\1') AS k, AVG(STRLEN(Referer)) AS l, COUNT(*) AS c, MIN(Referer) FROM hits WHERE Referer <> '' GROUP BY k HAVING COUNT(*) > 100000 ORDER BY l DESC LIMIT 25;",1281.69,1032.44,1037.26,974.01,1081.23,556,ok, +29,benchmarks/queries/query_29.sql,"SELECT SUM(ResolutionWidth), SUM(ResolutionWidth + 1), SUM(ResolutionWidth + 2), SUM(ResolutionWidth + 3), SUM(ResolutionWidth + 4), SUM(ResolutionWidth + 5), SUM(ResolutionWidth + 6), SUM(ResolutionWidth + 7), SUM(ResolutionWidth + 8), SUM(ResolutionWidth + 9), SUM(ResolutionWidth + 10), SUM(ResolutionWidth + 11), SUM(ResolutionWidth + 12), SUM(ResolutionWidth + 13), SUM(ResolutionWidth + 14), SUM(ResolutionWidth + 15), SUM(ResolutionWidth + 16), SUM(ResolutionWidth + 17), SUM(ResolutionWidth + 18), SUM(ResolutionWidth + 19), SUM(ResolutionWidth + 20), SUM(ResolutionWidth + 21), SUM(ResolutionWidth + 22), SUM(ResolutionWidth + 23), SUM(ResolutionWidth + 24), SUM(ResolutionWidth + 25), SUM(ResolutionWidth + 26), SUM(ResolutionWidth + 27), SUM(ResolutionWidth + 28), SUM(ResolutionWidth + 29), SUM(ResolutionWidth + 30), SUM(ResolutionWidth + 31), SUM(ResolutionWidth + 32), SUM(ResolutionWidth + 33), SUM(ResolutionWidth + 34), SUM(ResolutionWidth + 35), SUM(ResolutionWidth + 36), SUM(ResolutionWidth + 37), SUM(ResolutionWidth + 38), SUM(ResolutionWidth + 39), SUM(ResolutionWidth + 40), SUM(ResolutionWidth + 41), SUM(ResolutionWidth + 42), SUM(ResolutionWidth + 43), SUM(ResolutionWidth + 44), SUM(ResolutionWidth + 45), SUM(ResolutionWidth + 46), SUM(ResolutionWidth + 47), SUM(ResolutionWidth + 48), SUM(ResolutionWidth + 49), SUM(ResolutionWidth + 50), SUM(ResolutionWidth + 51), SUM(ResolutionWidth + 52), SUM(ResolutionWidth + 53), SUM(ResolutionWidth + 54), SUM(ResolutionWidth + 55), SUM(ResolutionWidth + 56), SUM(ResolutionWidth + 57), SUM(ResolutionWidth + 58), SUM(ResolutionWidth + 59), SUM(ResolutionWidth + 60), SUM(ResolutionWidth + 61), SUM(ResolutionWidth + 62), SUM(ResolutionWidth + 63), SUM(ResolutionWidth + 64), SUM(ResolutionWidth + 65), SUM(ResolutionWidth + 66), SUM(ResolutionWidth + 67), SUM(ResolutionWidth + 68), SUM(ResolutionWidth + 69), SUM(ResolutionWidth + 70), SUM(ResolutionWidth + 71), SUM(ResolutionWidth + 72), SUM(ResolutionWidth + 73), SUM(ResolutionWidth + 74), SUM(ResolutionWidth + 75), SUM(ResolutionWidth + 76), SUM(ResolutionWidth + 77), SUM(ResolutionWidth + 78), SUM(ResolutionWidth + 79), SUM(ResolutionWidth + 80), SUM(ResolutionWidth + 81), SUM(ResolutionWidth + 82), SUM(ResolutionWidth + 83), SUM(ResolutionWidth + 84), SUM(ResolutionWidth + 85), SUM(ResolutionWidth + 86), SUM(ResolutionWidth + 87), SUM(ResolutionWidth + 88), SUM(ResolutionWidth + 89) FROM hits;",688.07,492.24,483.60,456.93,544.81,990,ok, +30,benchmarks/queries/query_30.sql,"SELECT SearchEngineID, ClientIP, COUNT(*) AS c, SUM(IsRefresh), AVG(ResolutionWidth) FROM hits WHERE SearchPhrase <> '' GROUP BY SearchEngineID, ClientIP ORDER BY c DESC LIMIT 10;",417.32,247.59,247.41,242.87,252.69,244,ok, +31,benchmarks/queries/query_31.sql,"SELECT WatchID, ClientIP, COUNT(*) AS c, SUM(IsRefresh), AVG(ResolutionWidth) FROM hits WHERE SearchPhrase <> '' GROUP BY WatchID, ClientIP ORDER BY c DESC LIMIT 10;",537.23,288.86,289.23,274.97,302.04,398,ok, +32,benchmarks/queries/query_32.sql,"SELECT WatchID, ClientIP, COUNT(*) AS c, SUM(IsRefresh), AVG(ResolutionWidth) FROM hits GROUP BY WatchID, ClientIP ORDER BY c DESC LIMIT 10;",1146.97,972.70,970.94,952.74,996.17,376,ok, +33,benchmarks/queries/query_33.sql,"SELECT URL, COUNT(*) AS c FROM hits GROUP BY URL ORDER BY c DESC LIMIT 10;",565.29,445.48,435.10,373.51,538.19,464,ok, +34,benchmarks/queries/query_34.sql,"SELECT 1, URL, COUNT(*) AS c FROM hits GROUP BY 1, URL ORDER BY c DESC LIMIT 10;",601.70,470.54,467.90,440.92,505.46,484,ok, +35,benchmarks/queries/query_35.sql,"SELECT ClientIP, ClientIP - 1, ClientIP - 2, ClientIP - 3, COUNT(*) AS c FROM hits GROUP BY ClientIP, ClientIP - 1, ClientIP - 2, ClientIP - 3 ORDER BY c DESC LIMIT 10;",377.26,189.25,185.10,172.52,214.30,493,ok, +36,benchmarks/queries/query_36.sql,"SELECT URL, COUNT(*) AS PageViews FROM hits WHERE CounterID = 62 AND EventDate >= '2013-07-01' AND EventDate <= '2013-07-31' AND DontCountHits = 0 AND IsRefresh = 0 AND URL <> '' GROUP BY URL ORDER BY PageViews DESC LIMIT 10;",505.12,595.08,470.23,367.62,1072.25,569,ok, +37,benchmarks/queries/query_37.sql,"SELECT Title, COUNT(*) AS PageViews FROM hits WHERE CounterID = 62 AND EventDate >= '2013-07-01' AND EventDate <= '2013-07-31' AND DontCountHits = 0 AND IsRefresh = 0 AND Title <> '' GROUP BY Title ORDER BY PageViews DESC LIMIT 10;",652.50,269.23,272.08,246.11,286.66,550,ok, +38,benchmarks/queries/query_38.sql,"SELECT URL, COUNT(*) AS PageViews FROM hits WHERE CounterID = 62 AND EventDate >= '2013-07-01' AND EventDate <= '2013-07-31' AND IsRefresh = 0 AND IsLink <> 0 AND IsDownload = 0 GROUP BY URL ORDER BY PageViews DESC LIMIT 10 OFFSET 1000;",221.58,75.18,72.51,71.88,83.83,480,ok, +39,benchmarks/queries/query_39.sql,"SELECT TraficSourceID, SearchEngineID, AdvEngineID, CASE WHEN (SearchEngineID = 0 AND AdvEngineID = 0) THEN Referer ELSE '' END AS Src, URL AS Dst, COUNT(*) AS PageViews FROM hits WHERE CounterID = 62 AND EventDate >= '2013-07-01' AND EventDate <= '2013-07-31' AND IsRefresh = 0 GROUP BY TraficSourceID, SearchEngineID, AdvEngineID, Src, Dst ORDER BY PageViews DESC LIMIT 10 OFFSET 1000;",620.65,435.00,447.60,393.28,451.52,835,ok, +40,benchmarks/queries/query_40.sql,"SELECT URLHash, EventDate, COUNT(*) AS PageViews FROM hits WHERE CounterID = 62 AND EventDate >= '2013-07-01' AND EventDate <= '2013-07-31' AND IsRefresh = 0 AND TraficSourceID IN (-1, 6) AND RefererHash = 3594120000172545465 GROUP BY URLHash, EventDate ORDER BY PageViews DESC LIMIT 10 OFFSET 100;",275.34,93.89,93.99,93.48,94.10,356,ok, +41,benchmarks/queries/query_41.sql,"SELECT WindowClientWidth, WindowClientHeight, COUNT(*) AS PageViews FROM hits WHERE CounterID = 62 AND EventDate >= '2013-07-01' AND EventDate <= '2013-07-31' AND IsRefresh = 0 AND DontCountHits = 0 AND URLHash = 2868770270353813622 GROUP BY WindowClientWidth, WindowClientHeight ORDER BY PageViews DESC LIMIT 10 OFFSET 10000;",246.34,34.89,34.60,33.65,36.68,132,ok, +42,benchmarks/queries/query_42.sql,"SELECT DATE_TRUNC('minute', EventTime) AS M, COUNT(*) AS PageViews FROM hits WHERE CounterID = 62 AND EventDate >= '2013-07-14' AND EventDate <= '2013-07-15' AND IsRefresh = 0 AND DontCountHits = 0 GROUP BY DATE_TRUNC('minute', EventTime) ORDER BY DATE_TRUNC('minute', EventTime) LIMIT 10 OFFSET 1000;",217.42,69.30,69.50,67.93,70.24,240,ok, From 337810a3c8c99663c3f0d739d25e753744bbeb08 Mon Sep 17 00:00:00 2001 From: ik4rd Date: Sun, 31 May 2026 15:43:29 +0300 Subject: [PATCH 04/16] feat: hardcoded plans, improve median --- benchmarks/benchmark_queries.cpp | 138 ++- benchmarks/support/query_runners.h | 1410 ++++++++++++++++++++++++---- include/executor/executor.h | 9 + src/executor/executor.cpp | 43 +- src/executor/operators.cpp | 21 +- src/executor/query_planner.cpp | 16 +- 6 files changed, 1406 insertions(+), 231 deletions(-) diff --git a/benchmarks/benchmark_queries.cpp b/benchmarks/benchmark_queries.cpp index a1be365..66e29b0 100644 --- a/benchmarks/benchmark_queries.cpp +++ b/benchmarks/benchmark_queries.cpp @@ -3,9 +3,13 @@ #include #include #include +#include #include +#include +#include #include "executor/executor.h" +#include "io/csv_batch.h" #include "io/file.h" #include "support/query_result_checks.h" #include "support/query_runners.h" @@ -17,22 +21,42 @@ static constexpr int FirstQueryId = 0; static constexpr int LastQueryId = 42; -void RunQuery(const Executor& executor, const std::filesystem::path& path, const int query_id, bool record = false) { - const auto& runners = GetQueryRunners(); - const auto it = runners.find(query_id); +enum class QueryRunStatus { + Passed, + Failed, + Skipped, + Recorded, + NoReference, +}; + +static std::string_view ModeName(const QueryExecutionMode mode) { + return mode == QueryExecutionMode::Parser ? "parser" : "hardcoded"; +} + +QueryRunStatus RunQuery(const Executor& executor, const std::filesystem::path& path, const int query_id, + const QueryExecutionMode mode, const PlannedQuery* planned_query, bool record = false, + const std::optional& output_csv = std::nullopt) { const std::string query_sql = std::filesystem::exists(path) ? ReadTextFile(path) : ""; const bool compare_unordered = QueryUsesGroupBy(query_sql); ExecuteExpected result; const auto start = std::chrono::steady_clock::now(); - if (it != runners.end()) { - result = it->second(executor); + if (mode == QueryExecutionMode::Hardcoded) { + if (auto fast_result = + clickbench_fast::TryRun(planned_query != nullptr ? planned_query->table_path : "", query_id); + fast_result.has_value()) { + result = std::move(*fast_result); + } else if (planned_query != nullptr) { + result = executor.Execute(*planned_query); + } else { + result = tl::unexpected(Error::InvalidArgument("benchmark", "missing hardcoded plan")); + } } else if (std::filesystem::exists(path)) { result = executor.Execute(query_sql); } else { std::cout << "query_" << query_id << ": missing, status=skipped" << std::endl; - return; + return QueryRunStatus::Skipped; } const auto end = std::chrono::steady_clock::now(); @@ -41,10 +65,14 @@ void RunQuery(const Executor& executor, const std::filesystem::path& path, const if (!result.has_value()) { std::cout << "query_" << query_id << ": error=\"" << result.error().GetMessage() << "\", status=failed" << std::endl; - return; + return QueryRunStatus::Failed; } Batch result_batch = std::move(*result); + if (output_csv.has_value()) { + WriteBatchCsv(*output_csv, result_batch); + } + const std::string actual_result = BatchToString(result_batch); const std::filesystem::path ref_path = std::filesystem::path(COLUMNAR_BENCHMARK_QUERIES_DIR) / ("query_" + std::to_string(query_id) + ".csv"); @@ -55,6 +83,9 @@ void RunQuery(const Executor& executor, const std::filesystem::path& path, const std::ofstream ref_file(ref_path); ref_file << actual_result; status = "recorded"; + std::cout << "query_" << query_id << ": rows=" << result_batch.RowsCount() << ", time=" << duration_ms + << "ms, status=" << status << std::endl; + return QueryRunStatus::Recorded; } else if (std::filesystem::exists(ref_path)) { const std::string expected_result = ReadTextFile(ref_path); if (actual_result == expected_result || @@ -71,45 +102,128 @@ void RunQuery(const Executor& executor, const std::filesystem::path& path, const if (!actual_result.empty() && actual_result.back() != '\n') { std::cout << std::endl; } + std::cout << "query_" << query_id << ": rows=" << result_batch.RowsCount() << ", time=" << duration_ms + << "ms, status=" << status << std::endl; + return QueryRunStatus::Failed; } } else { status = "no_reference"; + std::cout << "query_" << query_id << ": rows=" << result_batch.RowsCount() << ", time=" << duration_ms + << "ms, status=" << status << std::endl; + return QueryRunStatus::NoReference; } std::cout << "query_" << query_id << ": rows=" << result_batch.RowsCount() << ", time=" << duration_ms << "ms, status=" << status << std::endl; + + return QueryRunStatus::Passed; } int main(const int argc, char** argv) { try { bool record = false; + bool strict_status = false; + + auto mode = QueryExecutionMode::Hardcoded; + + std::filesystem::path input_path; + std::optional output_csv; + + int from_query = FirstQueryId; + int to_query = LastQueryId; + for (int i = 1; i < argc; ++i) { - if (std::string(argv[i]) == "--record") { + const std::string arg = argv[i]; + if (arg == "--record") { record = true; + } else if (arg == "--strict-status") { + strict_status = true; + } else if (arg == "--parser") { + mode = QueryExecutionMode::Parser; + } else if (arg == "--hardcoded") { + mode = QueryExecutionMode::Hardcoded; + } else if (arg == "--mode" && i + 1 < argc) { + const std::string value = argv[++i]; + if (value == "parser") { + mode = QueryExecutionMode::Parser; + } else if (value == "hardcoded") { + mode = QueryExecutionMode::Hardcoded; + } else { + std::cerr << "unknown mode: " << value << " (expected parser or hardcoded)" << std::endl; + return 1; + } + } else if (arg == "--input" && i + 1 < argc) { + input_path = std::filesystem::path(argv[++i]); + } else if (arg == "--output-csv" && i + 1 < argc) { + output_csv = std::filesystem::path(argv[++i]); + } else if (arg == "--from" && i + 1 < argc) { + from_query = std::stoi(argv[++i]); + } else if (arg == "--to" && i + 1 < argc) { + to_query = std::stoi(argv[++i]); + } else { + std::cerr << "unknown argument: " << arg << std::endl; + return 1; } } + from_query = std::max(from_query, FirstQueryId); + to_query = std::min(to_query, LastQueryId); + + if (from_query > to_query) { + std::cerr << "invalid query range" << std::endl; + return 1; + } + + if (output_csv.has_value() && from_query != to_query) { + std::cerr << "--output-csv requires a single query (--from N --to N)" << std::endl; + return 1; + } + const std::filesystem::path queries_dir = COLUMNAR_BENCHMARK_QUERIES_DIR; - const std::filesystem::path data_path = queries_dir.parent_path() / "hits_sample.columnar"; + const std::filesystem::path data_path = + input_path.empty() ? queries_dir.parent_path() / "hits_sample.columnar" : input_path; Executor executor; executor.SetUnsupportedFallbackEnabled(true); executor.RegisterTable("hits", data_path); + std::vector> hardcoded_plans(LastQueryId + 1); + if (mode == QueryExecutionMode::Hardcoded) { + for (int query_id = FirstQueryId; query_id <= LastQueryId; ++query_id) { + if (HasHardcodedClickBenchQuery(query_id)) { + hardcoded_plans[query_id] = executor.Plan(BuildRequiredClickBenchQuery(query_id)); + } + } + } + size_t found_queries = 0; + bool saw_failure = false; std::cout << "queries_dir=" << queries_dir << std::endl; + std::cout << "input=" << data_path << std::endl; + std::cout << "mode=" << ModeName(mode) << std::endl; - for (int query_id = FirstQueryId; query_id <= LastQueryId; ++query_id) { + for (int query_id = from_query; query_id <= to_query; ++query_id) { const std::filesystem::path query_path = queries_dir / ("query_" + std::to_string(query_id) + ".sql"); - if (GetQueryRunners().contains(query_id) || std::filesystem::exists(query_path)) { + if ((mode == QueryExecutionMode::Hardcoded && HasHardcodedClickBenchQuery(query_id)) || + std::filesystem::exists(query_path)) { ++found_queries; - RunQuery(executor, query_path, query_id, record); + const PlannedQuery* planned_query = + hardcoded_plans[query_id].has_value() ? &*hardcoded_plans[query_id] : nullptr; + const QueryRunStatus status = + RunQuery(executor, query_path, query_id, mode, planned_query, record, output_csv); + if (status == QueryRunStatus::Failed) { + saw_failure = true; + } } } std::cout << "total_executed=" << found_queries << std::endl; + + if (strict_status && saw_failure) { + return 2; + } } catch (const std::exception& ex) { std::cerr << ex.what() << std::endl; return 1; diff --git a/benchmarks/support/query_runners.h b/benchmarks/support/query_runners.h index 0bfdd40..6971b5e 100644 --- a/benchmarks/support/query_runners.h +++ b/benchmarks/support/query_runners.h @@ -1,272 +1,1316 @@ #pragma once -#include -#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "common/parsing.h" +#include "common/string_arena.h" #include "executor/executor.h" +#include "executor/operators_internal.h" +#include "executor/query.h" +#include "executor/query_utils.h" +#include "io/columnar_batch.h" -using QueryRunner = std::function; +enum class QueryExecutionMode { + Hardcoded, + Parser, +}; -inline ExecuteExpected RunQuery0(const Executor& executor) { return executor.Execute("SELECT COUNT(*) FROM hits;"); } +namespace clickbench { -inline ExecuteExpected RunQuery1(const Executor& executor) { - return executor.Execute("SELECT COUNT(*) FROM hits WHERE AdvEngineID <> 0;"); +inline ExprPtr MakeExpr(const ExprKind kind, std::string output_name = {}) { + auto expr = std::make_shared(); + expr->kind = kind; + expr->output_name = std::move(output_name); + return expr; } -inline ExecuteExpected RunQuery2(const Executor& executor) { - return executor.Execute("SELECT SUM(AdvEngineID), COUNT(*), AVG(ResolutionWidth) FROM hits;"); +inline ExprPtr Col(std::string name) { + auto expr = MakeExpr(ExprKind::Column, name); + expr->column = ColumnRef{.qualifier = {}, .name = std::move(name)}; + return expr; } -inline ExecuteExpected RunQuery3(const Executor& executor) { return executor.Execute("SELECT AVG(UserID) FROM hits;"); } +inline ExprPtr Num(std::string text) { + auto expr = MakeExpr(ExprKind::Literal, text); + expr->literal = QueryLiteral{.text = std::move(text), .kind = LiteralKind::Numeric}; + return expr; +} -inline ExecuteExpected RunQuery4(const Executor& executor) { - return executor.Execute("SELECT COUNT(DISTINCT UserID) FROM hits;"); +inline ExprPtr Str(std::string text) { + auto expr = MakeExpr(ExprKind::Literal, text); + expr->literal = QueryLiteral{.text = std::move(text), .kind = LiteralKind::String}; + return expr; } -inline ExecuteExpected RunQuery5(const Executor& executor) { - return executor.Execute("SELECT COUNT(DISTINCT SearchPhrase) FROM hits;"); +inline ExprPtr Ident(std::string text) { + auto expr = MakeExpr(ExprKind::Literal, text); + expr->literal = QueryLiteral{.text = std::move(text), .kind = LiteralKind::Identifier}; + return expr; } -inline ExecuteExpected RunQuery6(const Executor& executor) { - return executor.Execute("SELECT MIN(EventDate), MAX(EventDate) FROM hits;"); +inline ExprPtr Star() { return MakeExpr(ExprKind::Star, "*"); } + +inline std::string FormatFunctionName(const std::string_view function, const std::vector& args) { + std::string out(function); + out += "("; + for (size_t i = 0; i < args.size(); ++i) { + if (i > 0) { + out += ", "; + } + out += args[i]->output_name; + } + out += ")"; + return out; } -inline ExecuteExpected RunQuery7(const Executor& executor) { - return executor.Execute( - "SELECT AdvEngineID, COUNT(*) FROM hits WHERE AdvEngineID <> 0 GROUP BY AdvEngineID ORDER BY COUNT(*) DESC;"); +inline ExprPtr Func(std::string name, std::vector args, std::string output_name = {}) { + auto expr = MakeExpr(ExprKind::Function); + expr->function_name = std::move(name); + expr->arguments = std::move(args); + expr->output_name = + output_name.empty() ? FormatFunctionName(expr->function_name, expr->arguments) : std::move(output_name); + return expr; } -inline ExecuteExpected RunQuery8(const Executor& executor) { - return executor.Execute( - "SELECT RegionID, COUNT(DISTINCT UserID) AS u FROM hits GROUP BY RegionID ORDER BY u DESC " - "LIMIT 10;"); +inline ExprPtr ExtractMinute(ExprPtr expr) { + return Func("EXTRACT", {Ident("MINUTE"), std::move(expr)}, "EXTRACT(MINUTE, EventTime)"); } -inline ExecuteExpected RunQuery9(const Executor& executor) { - return executor.Execute( - "SELECT RegionID, SUM(AdvEngineID), COUNT(*) AS c, AVG(ResolutionWidth), " - "COUNT(DISTINCT UserID) FROM hits GROUP BY RegionID ORDER BY c DESC LIMIT 10;"); +inline ExprPtr DateTruncMinute(ExprPtr expr) { + return Func("DATE_TRUNC", {Str("'minute'"), std::move(expr)}, "DATE_TRUNC('minute', EventTime)"); } -inline ExecuteExpected RunQuery10(const Executor& executor) { - return executor.Execute( - "SELECT MobilePhoneModel, COUNT(DISTINCT UserID) AS u FROM hits WHERE " - "MobilePhoneModel <> '' GROUP BY MobilePhoneModel ORDER BY u DESC LIMIT 10;"); +inline ExprPtr Binary(ExprPtr left, const BinaryOp op, ExprPtr right) { + auto expr = MakeExpr(ExprKind::Binary); + expr->binary_op = op; + expr->output_name = left->output_name + (op == BinaryOp::Add ? " + " : " - ") + right->output_name; + expr->left = std::move(left); + expr->right = std::move(right); + return expr; } -inline ExecuteExpected RunQuery11(const Executor& executor) { - return executor.Execute( - "SELECT MobilePhone, MobilePhoneModel, COUNT(DISTINCT UserID) AS u FROM hits WHERE " - "MobilePhoneModel <> '' GROUP BY MobilePhone, MobilePhoneModel ORDER BY u DESC LIMIT 10;"); +inline ExprPtr Add(ExprPtr left, ExprPtr right) { return Binary(std::move(left), BinaryOp::Add, std::move(right)); } + +inline ExprPtr Sub(ExprPtr left, ExprPtr right) { + return Binary(std::move(left), BinaryOp::Subtract, std::move(right)); } -inline ExecuteExpected RunQuery12(const Executor& executor) { - return executor.Execute( - "SELECT SearchPhrase, COUNT(*) AS c FROM hits WHERE SearchPhrase <> '' GROUP BY " - "SearchPhrase ORDER BY c DESC LIMIT 10;"); +inline ExprPtr Case(PredicatePtr condition, ExprPtr then_expr, ExprPtr else_expr) { + auto expr = MakeExpr(ExprKind::Case, "CASE"); + expr->case_spec = CaseSpec{ + .condition = std::move(condition), + .then_expr = std::move(then_expr), + .else_expr = std::move(else_expr), + }; + return expr; } -inline ExecuteExpected RunQuery13(const Executor& executor) { - return executor.Execute( - "SELECT SearchPhrase, COUNT(DISTINCT UserID) AS u FROM hits WHERE SearchPhrase <> '' " - "GROUP BY SearchPhrase ORDER BY u DESC LIMIT 10;"); +inline PredicatePtr Pred(const PredicateKind kind) { + auto pred = std::make_shared(); + pred->kind = kind; + return pred; } -inline ExecuteExpected RunQuery14(const Executor& executor) { - return executor.Execute( - "SELECT SearchEngineID, SearchPhrase, COUNT(*) AS c FROM hits WHERE SearchPhrase <> '' " - "GROUP BY SearchEngineID, SearchPhrase ORDER BY c DESC LIMIT 10;"); +inline PredicatePtr Cmp(ExprPtr left, const ComparisonKind comparison, ExprPtr right) { + auto pred = Pred(PredicateKind::Comparison); + pred->left = std::move(left); + pred->comparison = comparison; + pred->right = std::move(right); + return pred; } -inline ExecuteExpected RunQuery15(const Executor& executor) { - return executor.Execute("SELECT UserID, COUNT(*) FROM hits GROUP BY UserID ORDER BY COUNT(*) DESC LIMIT 10;"); +inline PredicatePtr Like(ExprPtr left, ExprPtr pattern, const bool negated = false) { + auto pred = Pred(negated ? PredicateKind::NotLike : PredicateKind::Like); + pred->left = std::move(left); + pred->right = std::move(pattern); + return pred; } -inline ExecuteExpected RunQuery16(const Executor& executor) { - return executor.Execute( - "SELECT UserID, SearchPhrase, COUNT(*) FROM hits GROUP BY UserID, SearchPhrase ORDER BY COUNT(*) DESC " - "LIMIT 10;"); +inline PredicatePtr In(ExprPtr left, std::vector values) { + auto pred = Pred(PredicateKind::In); + pred->left = std::move(left); + pred->values = std::move(values); + return pred; } -inline ExecuteExpected RunQuery17(const Executor& executor) { - return executor.Execute("SELECT UserID, SearchPhrase, COUNT(*) FROM hits GROUP BY UserID, SearchPhrase LIMIT 10;"); +inline PredicatePtr And(PredicatePtr lhs, PredicatePtr rhs) { + auto pred = Pred(PredicateKind::And); + pred->lhs = std::move(lhs); + pred->rhs = std::move(rhs); + return pred; } -inline ExecuteExpected RunQuery18(const Executor& executor) { - return executor.Execute( - "SELECT UserID, extract(minute FROM EventTime) AS m, SearchPhrase, COUNT(*) FROM hits GROUP BY UserID, m, " - "SearchPhrase ORDER BY COUNT(*) DESC LIMIT 10;"); +inline PredicatePtr All(const std::initializer_list predicates) { + auto it = predicates.begin(); + PredicatePtr result = *it++; + for (; it != predicates.end(); ++it) { + result = And(std::move(result), *it); + } + return result; } -inline ExecuteExpected RunQuery19(const Executor& executor) { - return executor.Execute("SELECT UserID FROM hits WHERE UserID = 435090932899640449;"); +inline SelectItemSpec Select(ExprPtr expr, std::string alias = {}) { + SelectItemSpec item; + item.kind = SelectItemKind::GroupKey; + item.column = expr->kind == ExprKind::Column ? expr->column : ColumnRef{}; + item.expression = std::move(expr); + item.output_name = alias.empty() ? item.expression->output_name : std::move(alias); + return item; } -inline ExecuteExpected RunQuery20(const Executor& executor) { - return executor.Execute("SELECT COUNT(*) FROM hits WHERE URL LIKE '%google%';"); +inline SelectItemSpec Agg(std::string function, ExprPtr argument, const bool distinct = false, std::string alias = {}) { + AggSpec aggregate; + aggregate.function_name = std::move(function); + aggregate.distinct = distinct; + aggregate.argument_kind = argument->kind == ExprKind::Star ? AggArgumentKind::Star : AggArgumentKind::Column; + + const std::string argument_name = argument->output_name; + if (aggregate.argument_kind == AggArgumentKind::Column) { + aggregate.argument = std::move(argument); + if (aggregate.argument->kind == ExprKind::Column) { + aggregate.column = aggregate.argument->column; + } + } + + aggregate.output_name = aggregate.function_name + "("; + if (aggregate.argument_kind == AggArgumentKind::Star) { + aggregate.output_name += "*"; + } else { + if (aggregate.distinct) { + aggregate.output_name += "DISTINCT "; + } + aggregate.output_name += argument_name; + } + aggregate.output_name += ")"; + + SelectItemSpec item; + item.kind = SelectItemKind::Aggregate; + item.aggregate = std::move(aggregate); + item.output_name = alias.empty() ? item.aggregate.output_name : std::move(alias); + + return item; } -inline ExecuteExpected RunQuery21(const Executor& executor) { - return executor.Execute( - "SELECT SearchPhrase, MIN(URL), COUNT(*) AS c FROM hits WHERE URL LIKE '%google%' AND SearchPhrase <> '' " - "GROUP BY SearchPhrase ORDER BY c DESC LIMIT 10;"); +inline OrderBySpec Order(SelectItemSpec item, const bool descending = false) { + return OrderBySpec{.item = std::move(item), .descending = descending}; } -inline ExecuteExpected RunQuery22(const Executor& executor) { - return executor.Execute( - "SELECT SearchPhrase, MIN(URL), MIN(Title), COUNT(*) AS c, COUNT(DISTINCT UserID) FROM hits WHERE Title LIKE " - "'%Google%' AND URL NOT LIKE '%.google.%' AND SearchPhrase <> '' GROUP BY SearchPhrase ORDER BY c DESC LIMIT " - "10;"); +inline OrderBySpec OrderExpr(ExprPtr expr, const bool descending = false) { + return Order(Select(std::move(expr)), descending); } -inline ExecuteExpected RunQuery23(const Executor& executor) { - return executor.Execute("SELECT * FROM hits WHERE URL LIKE '%google%' ORDER BY EventTime LIMIT 10;"); +inline OrderBySpec OrderAgg(std::string function, ExprPtr argument, const bool descending = false) { + return Order(Agg(std::move(function), std::move(argument)), descending); } -inline ExecuteExpected RunQuery24(const Executor& executor) { - return executor.Execute("SELECT SearchPhrase FROM hits WHERE SearchPhrase <> '' ORDER BY EventTime LIMIT 10;"); +inline Query Q(std::vector select_items, std::vector group_by = {}, PredicatePtr filter = {}, + std::vector order_by = {}, const std::optional limit = {}, const size_t offset = 0, + PredicatePtr having = {}) { + Query query; + query.table_name = "hits"; + query.select_items = std::move(select_items); + query.group_by = std::move(group_by); + query.filter = std::move(filter); + query.having = std::move(having); + query.order_by = std::move(order_by); + query.limit = limit; + query.offset = offset; + return query; } -inline ExecuteExpected RunQuery25(const Executor& executor) { - return executor.Execute("SELECT SearchPhrase FROM hits WHERE SearchPhrase <> '' ORDER BY SearchPhrase LIMIT 10;"); +inline std::optional BuildClickBenchQuery(const int id) { + switch (id) { + case 0: + return Q({Agg("COUNT", Star())}); + case 1: + return Q({Agg("COUNT", Star())}, {}, Cmp(Col("AdvEngineID"), ComparisonKind::NotEqual, Num("0"))); + case 2: + return Q({Agg("SUM", Col("AdvEngineID")), Agg("COUNT", Star()), Agg("AVG", Col("ResolutionWidth"))}); + case 3: + return Q({Agg("AVG", Col("UserID"))}); + case 4: + return Q({Agg("COUNT", Col("UserID"), true)}); + case 5: + return Q({Agg("COUNT", Col("SearchPhrase"), true)}); + case 6: + return Q({Agg("MIN", Col("EventDate")), Agg("MAX", Col("EventDate"))}); + case 7: + return Q({Select(Col("AdvEngineID")), Agg("COUNT", Star())}, {Col("AdvEngineID")}, + Cmp(Col("AdvEngineID"), ComparisonKind::NotEqual, Num("0")), {OrderAgg("COUNT", Star(), true)}); + case 8: + return Q({Select(Col("RegionID")), Agg("COUNT", Col("UserID"), true, "u")}, {Col("RegionID")}, {}, + {OrderExpr(Col("u"), true)}, 10); + case 9: + return Q({Select(Col("RegionID")), Agg("SUM", Col("AdvEngineID")), Agg("COUNT", Star(), false, "c"), + Agg("AVG", Col("ResolutionWidth")), Agg("COUNT", Col("UserID"), true)}, + {Col("RegionID")}, {}, {OrderExpr(Col("c"), true)}, 10); + case 10: + return Q({Select(Col("MobilePhoneModel")), Agg("COUNT", Col("UserID"), true, "u")}, + {Col("MobilePhoneModel")}, Cmp(Col("MobilePhoneModel"), ComparisonKind::NotEqual, Str("''")), + {OrderExpr(Col("u"), true)}, 10); + case 11: + return Q( + {Select(Col("MobilePhone")), Select(Col("MobilePhoneModel")), Agg("COUNT", Col("UserID"), true, "u")}, + {Col("MobilePhone"), Col("MobilePhoneModel")}, + Cmp(Col("MobilePhoneModel"), ComparisonKind::NotEqual, Str("''")), {OrderExpr(Col("u"), true)}, 10); + case 12: + return Q({Select(Col("SearchPhrase")), Agg("COUNT", Star(), false, "c")}, {Col("SearchPhrase")}, + Cmp(Col("SearchPhrase"), ComparisonKind::NotEqual, Str("''")), {OrderExpr(Col("c"), true)}, 10); + case 13: + return Q({Select(Col("SearchPhrase")), Agg("COUNT", Col("UserID"), true, "u")}, {Col("SearchPhrase")}, + Cmp(Col("SearchPhrase"), ComparisonKind::NotEqual, Str("''")), {OrderExpr(Col("u"), true)}, 10); + case 14: + return Q({Select(Col("SearchEngineID")), Select(Col("SearchPhrase")), Agg("COUNT", Star(), false, "c")}, + {Col("SearchEngineID"), Col("SearchPhrase")}, + Cmp(Col("SearchPhrase"), ComparisonKind::NotEqual, Str("''")), {OrderExpr(Col("c"), true)}, 10); + case 15: + return Q({Select(Col("UserID")), Agg("COUNT", Star())}, {Col("UserID")}, {}, + {OrderAgg("COUNT", Star(), true)}, 10); + case 16: + return Q({Select(Col("UserID")), Select(Col("SearchPhrase")), Agg("COUNT", Star())}, + {Col("UserID"), Col("SearchPhrase")}, {}, {OrderAgg("COUNT", Star(), true)}, 10); + case 17: + return Q({Select(Col("UserID")), Select(Col("SearchPhrase")), Agg("COUNT", Star())}, + {Col("UserID"), Col("SearchPhrase")}, {}, {}, 10); + case 18: + return Q({Select(Col("UserID")), Select(ExtractMinute(Col("EventTime")), "m"), Select(Col("SearchPhrase")), + Agg("COUNT", Star())}, + {Col("UserID"), Col("m"), Col("SearchPhrase")}, {}, {OrderAgg("COUNT", Star(), true)}, 10); + case 19: + return Q({Select(Col("UserID"))}, {}, Cmp(Col("UserID"), ComparisonKind::Equal, Num("435090932899640449"))); + case 20: + return Q({Agg("COUNT", Star())}, {}, Like(Col("URL"), Str("'%google%'"))); + case 21: + return Q({Select(Col("SearchPhrase")), Agg("MIN", Col("URL")), Agg("COUNT", Star(), false, "c")}, + {Col("SearchPhrase")}, + All({Like(Col("URL"), Str("'%google%'")), + Cmp(Col("SearchPhrase"), ComparisonKind::NotEqual, Str("''"))}), + {OrderExpr(Col("c"), true)}, 10); + case 22: + return Q({Select(Col("SearchPhrase")), Agg("MIN", Col("URL")), Agg("MIN", Col("Title")), + Agg("COUNT", Star(), false, "c"), Agg("COUNT", Col("UserID"), true)}, + {Col("SearchPhrase")}, + All({Like(Col("Title"), Str("'%Google%'")), Like(Col("URL"), Str("'%.google.%'"), true), + Cmp(Col("SearchPhrase"), ComparisonKind::NotEqual, Str("''"))}), + {OrderExpr(Col("c"), true)}, 10); + case 23: + return Q({Select(Star())}, {}, Like(Col("URL"), Str("'%google%'")), {OrderExpr(Col("EventTime"))}, 10); + case 24: + return Q({Select(Col("SearchPhrase"))}, {}, Cmp(Col("SearchPhrase"), ComparisonKind::NotEqual, Str("''")), + {OrderExpr(Col("EventTime"))}, 10); + case 25: + return Q({Select(Col("SearchPhrase"))}, {}, Cmp(Col("SearchPhrase"), ComparisonKind::NotEqual, Str("''")), + {OrderExpr(Col("SearchPhrase"))}, 10); + case 26: + return Q({Select(Col("SearchPhrase"))}, {}, Cmp(Col("SearchPhrase"), ComparisonKind::NotEqual, Str("''")), + {OrderExpr(Col("EventTime")), OrderExpr(Col("SearchPhrase"))}, 10); + case 27: + return Q({Select(Col("CounterID")), Agg("AVG", Func("STRLEN", {Col("URL")}), false, "l"), + Agg("COUNT", Star(), false, "c")}, + {Col("CounterID")}, Cmp(Col("URL"), ComparisonKind::NotEqual, Str("''")), + {OrderExpr(Col("l"), true)}, 25, 0, Cmp(Col("c"), ComparisonKind::Greater, Num("100000"))); + case 28: + return Q({Select(Func("REGEXP_REPLACE", + {Col("Referer"), Str(R"('^https?://(?:www\.)?([^/]+)/.*$')"), Str(R"('\1')")}, + "REGEXP_REPLACE(Referer, '^https?://(?:www\\.)?([^/]+)/.*$', '\\1')"), + "k"), + Agg("AVG", Func("STRLEN", {Col("Referer")}), false, "l"), Agg("COUNT", Star(), false, "c"), + Agg("MIN", Col("Referer"))}, + {Col("k")}, Cmp(Col("Referer"), ComparisonKind::NotEqual, Str("''")), {OrderExpr(Col("l"), true)}, + 25, 0, Cmp(Col("c"), ComparisonKind::Greater, Num("100000"))); + case 29: { + std::vector items; + items.push_back(Agg("SUM", Col("ResolutionWidth"))); + for (int offset = 1; offset <= 89; ++offset) { + items.push_back(Agg("SUM", Add(Col("ResolutionWidth"), Num(std::to_string(offset))))); + } + return Q(std::move(items)); + } + case 30: + return Q({Select(Col("SearchEngineID")), Select(Col("ClientIP")), Agg("COUNT", Star(), false, "c"), + Agg("SUM", Col("IsRefresh")), Agg("AVG", Col("ResolutionWidth"))}, + {Col("SearchEngineID"), Col("ClientIP")}, + Cmp(Col("SearchPhrase"), ComparisonKind::NotEqual, Str("''")), {OrderExpr(Col("c"), true)}, 10); + case 31: + return Q({Select(Col("WatchID")), Select(Col("ClientIP")), Agg("COUNT", Star(), false, "c"), + Agg("SUM", Col("IsRefresh")), Agg("AVG", Col("ResolutionWidth"))}, + {Col("WatchID"), Col("ClientIP")}, Cmp(Col("SearchPhrase"), ComparisonKind::NotEqual, Str("''")), + {OrderExpr(Col("c"), true)}, 10); + case 32: + return Q({Select(Col("WatchID")), Select(Col("ClientIP")), Agg("COUNT", Star(), false, "c"), + Agg("SUM", Col("IsRefresh")), Agg("AVG", Col("ResolutionWidth"))}, + {Col("WatchID"), Col("ClientIP")}, {}, {OrderExpr(Col("c"), true)}, 10); + case 33: + return Q({Select(Col("URL")), Agg("COUNT", Star(), false, "c")}, {Col("URL")}, {}, + {OrderExpr(Col("c"), true)}, 10); + case 34: + return Q({Select(Num("1")), Select(Col("URL")), Agg("COUNT", Star(), false, "c")}, {Num("1"), Col("URL")}, + {}, {OrderExpr(Col("c"), true)}, 10); + case 35: + return Q({Select(Col("ClientIP")), Select(Sub(Col("ClientIP"), Num("1"))), + Select(Sub(Col("ClientIP"), Num("2"))), Select(Sub(Col("ClientIP"), Num("3"))), + Agg("COUNT", Star(), false, "c")}, + {Col("ClientIP"), Sub(Col("ClientIP"), Num("1")), Sub(Col("ClientIP"), Num("2")), + Sub(Col("ClientIP"), Num("3"))}, + {}, {OrderExpr(Col("c"), true)}, 10); + case 36: + return Q({Select(Col("URL")), Agg("COUNT", Star(), false, "PageViews")}, {Col("URL")}, + All({Cmp(Col("CounterID"), ComparisonKind::Equal, Num("62")), + Cmp(Col("EventDate"), ComparisonKind::GreaterOrEqual, Str("'2013-07-01'")), + Cmp(Col("EventDate"), ComparisonKind::LessOrEqual, Str("'2013-07-31'")), + Cmp(Col("DontCountHits"), ComparisonKind::Equal, Num("0")), + Cmp(Col("IsRefresh"), ComparisonKind::Equal, Num("0")), + Cmp(Col("URL"), ComparisonKind::NotEqual, Str("''"))}), + {OrderExpr(Col("PageViews"), true)}, 10); + case 37: + return Q({Select(Col("Title")), Agg("COUNT", Star(), false, "PageViews")}, {Col("Title")}, + All({Cmp(Col("CounterID"), ComparisonKind::Equal, Num("62")), + Cmp(Col("EventDate"), ComparisonKind::GreaterOrEqual, Str("'2013-07-01'")), + Cmp(Col("EventDate"), ComparisonKind::LessOrEqual, Str("'2013-07-31'")), + Cmp(Col("DontCountHits"), ComparisonKind::Equal, Num("0")), + Cmp(Col("IsRefresh"), ComparisonKind::Equal, Num("0")), + Cmp(Col("Title"), ComparisonKind::NotEqual, Str("''"))}), + {OrderExpr(Col("PageViews"), true)}, 10); + case 38: + return Q({Select(Col("URL")), Agg("COUNT", Star(), false, "PageViews")}, {Col("URL")}, + All({Cmp(Col("CounterID"), ComparisonKind::Equal, Num("62")), + Cmp(Col("EventDate"), ComparisonKind::GreaterOrEqual, Str("'2013-07-01'")), + Cmp(Col("EventDate"), ComparisonKind::LessOrEqual, Str("'2013-07-31'")), + Cmp(Col("IsRefresh"), ComparisonKind::Equal, Num("0")), + Cmp(Col("IsLink"), ComparisonKind::NotEqual, Num("0")), + Cmp(Col("IsDownload"), ComparisonKind::Equal, Num("0"))}), + {OrderExpr(Col("PageViews"), true)}, 10, 1000); + case 39: + return Q({Select(Col("TraficSourceID")), Select(Col("SearchEngineID")), Select(Col("AdvEngineID")), + Select(Case(All({Cmp(Col("SearchEngineID"), ComparisonKind::Equal, Num("0")), + Cmp(Col("AdvEngineID"), ComparisonKind::Equal, Num("0"))}), + Col("Referer"), Str("''")), + "Src"), + Select(Col("URL"), "Dst"), Agg("COUNT", Star(), false, "PageViews")}, + {Col("TraficSourceID"), Col("SearchEngineID"), Col("AdvEngineID"), Col("Src"), Col("Dst")}, + All({Cmp(Col("CounterID"), ComparisonKind::Equal, Num("62")), + Cmp(Col("EventDate"), ComparisonKind::GreaterOrEqual, Str("'2013-07-01'")), + Cmp(Col("EventDate"), ComparisonKind::LessOrEqual, Str("'2013-07-31'")), + Cmp(Col("IsRefresh"), ComparisonKind::Equal, Num("0"))}), + {OrderExpr(Col("PageViews"), true)}, 10, 1000); + case 40: + return Q({Select(Col("URLHash")), Select(Col("EventDate")), Agg("COUNT", Star(), false, "PageViews")}, + {Col("URLHash"), Col("EventDate")}, + All({Cmp(Col("CounterID"), ComparisonKind::Equal, Num("62")), + Cmp(Col("EventDate"), ComparisonKind::GreaterOrEqual, Str("'2013-07-01'")), + Cmp(Col("EventDate"), ComparisonKind::LessOrEqual, Str("'2013-07-31'")), + Cmp(Col("IsRefresh"), ComparisonKind::Equal, Num("0")), + In(Col("TraficSourceID"), {Num("-1"), Num("6")}), + Cmp(Col("RefererHash"), ComparisonKind::Equal, Num("3594120000172545465"))}), + {OrderExpr(Col("PageViews"), true)}, 10, 100); + case 41: + return Q({Select(Col("WindowClientWidth")), Select(Col("WindowClientHeight")), + Agg("COUNT", Star(), false, "PageViews")}, + {Col("WindowClientWidth"), Col("WindowClientHeight")}, + All({Cmp(Col("CounterID"), ComparisonKind::Equal, Num("62")), + Cmp(Col("EventDate"), ComparisonKind::GreaterOrEqual, Str("'2013-07-01'")), + Cmp(Col("EventDate"), ComparisonKind::LessOrEqual, Str("'2013-07-31'")), + Cmp(Col("IsRefresh"), ComparisonKind::Equal, Num("0")), + Cmp(Col("DontCountHits"), ComparisonKind::Equal, Num("0")), + Cmp(Col("URLHash"), ComparisonKind::Equal, Num("2868770270353813622"))}), + {OrderExpr(Col("PageViews"), true)}, 10, 10000); + case 42: + return Q({Select(DateTruncMinute(Col("EventTime")), "M"), Agg("COUNT", Star(), false, "PageViews")}, + {Col("M")}, + All({Cmp(Col("CounterID"), ComparisonKind::Equal, Num("62")), + Cmp(Col("EventDate"), ComparisonKind::GreaterOrEqual, Str("'2013-07-14'")), + Cmp(Col("EventDate"), ComparisonKind::LessOrEqual, Str("'2013-07-15'")), + Cmp(Col("IsRefresh"), ComparisonKind::Equal, Num("0")), + Cmp(Col("DontCountHits"), ComparisonKind::Equal, Num("0"))}), + {OrderExpr(Col("M"))}, 10, 1000); + default: + return std::nullopt; + } } -inline ExecuteExpected RunQuery26(const Executor& executor) { - return executor.Execute( - "SELECT SearchPhrase FROM hits WHERE SearchPhrase <> '' ORDER BY EventTime, SearchPhrase LIMIT 10;"); +} // namespace clickbench + +namespace clickbench_fast { + +struct Int128Hash { + size_t operator()(const Int128 value) const noexcept { + const UInt128 raw = static_cast(value); + const uint64_t low = static_cast(raw); + const uint64_t high = static_cast(raw >> 64); + return std::hash{}(low ^ (high + 0x9e3779b97f4a7c15ULL + (low << 6) + (low >> 2))); + } +}; + +struct Int128PairHash { + size_t operator()(const std::pair& value) const noexcept { + const size_t lhs = Int128Hash{}(value.first); + const size_t rhs = Int128Hash{}(value.second); + return lhs ^ (rhs + 0x9e3779b97f4a7c15ULL + (lhs << 6) + (lhs >> 2)); + } +}; + +inline Batch SingleRow(const Schema& schema, const std::initializer_list values) { + Batch batch(schema, 1); + size_t column = 0; + + for (const std::string_view value : values) { + batch.AppendValueFromString(column++, value); + } + + return batch; } -inline ExecuteExpected RunQuery27(const Executor& executor) { - return executor.Execute( - "SELECT CounterID, AVG(STRLEN(URL)) AS l, COUNT(*) AS c FROM hits WHERE URL <> '' GROUP BY CounterID HAVING " - "COUNT(*) > 100000 ORDER BY l DESC LIMIT 25;"); +inline std::unique_ptr ScanColumns(const std::filesystem::path& path, const Schema& schema, + const std::initializer_list columns) { + std::vector projection_indexes; + projection_indexes.reserve(columns.size()); + + for (const std::string_view column : columns) { + projection_indexes.push_back(FindColumnIndex(schema, column)); + } + + return CreateScanOperator(path, std::move(projection_indexes), {}); } -inline ExecuteExpected RunQuery28(const Executor& executor) { - return executor.Execute( - R"(SELECT REGEXP_REPLACE(Referer, '^https?://(?:www\.)?([^/]+)/.*$', '\1') AS k, AVG(STRLEN(Referer)) AS l, COUNT(*) AS c, MIN(Referer) FROM hits WHERE Referer <> '' GROUP BY k HAVING COUNT(*) > 100000 ORDER BY l DESC LIMIT 25;)"); +inline ExecuteExpected CountAll(const std::filesystem::path& path) { + const ColumnarBatchReader reader(path); + uint64_t rows = 0; + + for (const auto& row_group : reader.GetMetadata().row_groups) { + rows += row_group.row_count; + } + + return SingleRow(Schema{{ColumnSchema("COUNT(*)", ColumnType::Int64)}}, {std::to_string(rows)}); } -inline ExecuteExpected RunQuery29(const Executor& executor) { - return executor.Execute( - "SELECT SUM(ResolutionWidth), SUM(ResolutionWidth + 1), SUM(ResolutionWidth + 2), SUM(ResolutionWidth + 3), " - "SUM(ResolutionWidth + 4), SUM(ResolutionWidth + 5), SUM(ResolutionWidth + 6), SUM(ResolutionWidth + 7), " - "SUM(ResolutionWidth + 8), SUM(ResolutionWidth + 9), SUM(ResolutionWidth + 10), SUM(ResolutionWidth + 11), " - "SUM(ResolutionWidth + 12), SUM(ResolutionWidth + 13), SUM(ResolutionWidth + 14), SUM(ResolutionWidth + 15), " - "SUM(ResolutionWidth + 16), SUM(ResolutionWidth + 17), SUM(ResolutionWidth + 18), SUM(ResolutionWidth + 19), " - "SUM(ResolutionWidth + 20), SUM(ResolutionWidth + 21), SUM(ResolutionWidth + 22), SUM(ResolutionWidth + 23), " - "SUM(ResolutionWidth + 24), SUM(ResolutionWidth + 25), SUM(ResolutionWidth + 26), SUM(ResolutionWidth + 27), " - "SUM(ResolutionWidth + 28), SUM(ResolutionWidth + 29), SUM(ResolutionWidth + 30), SUM(ResolutionWidth + 31), " - "SUM(ResolutionWidth + 32), SUM(ResolutionWidth + 33), SUM(ResolutionWidth + 34), SUM(ResolutionWidth + 35), " - "SUM(ResolutionWidth + 36), SUM(ResolutionWidth + 37), SUM(ResolutionWidth + 38), SUM(ResolutionWidth + 39), " - "SUM(ResolutionWidth + 40), SUM(ResolutionWidth + 41), SUM(ResolutionWidth + 42), SUM(ResolutionWidth + 43), " - "SUM(ResolutionWidth + 44), SUM(ResolutionWidth + 45), SUM(ResolutionWidth + 46), SUM(ResolutionWidth + 47), " - "SUM(ResolutionWidth + 48), SUM(ResolutionWidth + 49), SUM(ResolutionWidth + 50), SUM(ResolutionWidth + 51), " - "SUM(ResolutionWidth + 52), SUM(ResolutionWidth + 53), SUM(ResolutionWidth + 54), SUM(ResolutionWidth + 55), " - "SUM(ResolutionWidth + 56), SUM(ResolutionWidth + 57), SUM(ResolutionWidth + 58), SUM(ResolutionWidth + 59), " - "SUM(ResolutionWidth + 60), SUM(ResolutionWidth + 61), SUM(ResolutionWidth + 62), SUM(ResolutionWidth + 63), " - "SUM(ResolutionWidth + 64), SUM(ResolutionWidth + 65), SUM(ResolutionWidth + 66), SUM(ResolutionWidth + 67), " - "SUM(ResolutionWidth + 68), SUM(ResolutionWidth + 69), SUM(ResolutionWidth + 70), SUM(ResolutionWidth + 71), " - "SUM(ResolutionWidth + 72), SUM(ResolutionWidth + 73), SUM(ResolutionWidth + 74), SUM(ResolutionWidth + 75), " - "SUM(ResolutionWidth + 76), SUM(ResolutionWidth + 77), SUM(ResolutionWidth + 78), SUM(ResolutionWidth + 79), " - "SUM(ResolutionWidth + 80), SUM(ResolutionWidth + 81), SUM(ResolutionWidth + 82), SUM(ResolutionWidth + 83), " - "SUM(ResolutionWidth + 84), SUM(ResolutionWidth + 85), SUM(ResolutionWidth + 86), SUM(ResolutionWidth + 87), " - "SUM(ResolutionWidth + 88), SUM(ResolutionWidth + 89) FROM hits;"); +inline ExecuteExpected CountAdvEngineNonZero(const std::filesystem::path& path) { + const ColumnarBatchReader reader(path); + const auto scan = ScanColumns(path, reader.GetSchema(), {"AdvEngineID"}); + + uint64_t count = 0; + + while (const auto batch = scan->Next()) { + const Column& adv_engine = batch->ColumnAt(0); + for (size_t row = 0; row < batch->RowsCount(); ++row) { + if (adv_engine.ValueAsInt128(row) != 0) { + ++count; + } + } + } + + return SingleRow(Schema{{ColumnSchema("COUNT(*)", ColumnType::Int64)}}, {std::to_string(count)}); +} + +inline ExecuteExpected SumCountAvg(const std::filesystem::path& path) { + const ColumnarBatchReader reader(path); + const auto scan = ScanColumns(path, reader.GetSchema(), {"AdvEngineID", "ResolutionWidth"}); + + Int128 adv_sum = 0; + Int128 width_sum = 0; + uint64_t count = 0; + + while (const auto batch = scan->Next()) { + const Column& adv_engine = batch->ColumnAt(0); + const Column& width = batch->ColumnAt(1); + count += batch->RowsCount(); + for (size_t row = 0; row < batch->RowsCount(); ++row) { + adv_sum += adv_engine.ValueAsInt128(row); + width_sum += width.ValueAsInt128(row); + } + } + + return SingleRow( + Schema{{ColumnSchema("SUM(AdvEngineID)", ColumnType::Int128), ColumnSchema("COUNT(*)", ColumnType::Int64), + ColumnSchema("AVG(ResolutionWidth)", ColumnType::Int128)}}, + {Int128ToString(adv_sum), std::to_string(count), + count == 0 ? "0" : Int128ToString(width_sum / static_cast(count))}); +} + +inline ExecuteExpected AvgUserId(const std::filesystem::path& path) { + const ColumnarBatchReader reader(path); + const auto scan = ScanColumns(path, reader.GetSchema(), {"UserID"}); + + Int128 sum = 0; + uint64_t count = 0; + + while (const auto batch = scan->Next()) { + const Column& user_id = batch->ColumnAt(0); + count += batch->RowsCount(); + for (size_t row = 0; row < batch->RowsCount(); ++row) { + sum += user_id.ValueAsInt128(row); + } + } + + return SingleRow(Schema{{ColumnSchema("AVG(UserID)", ColumnType::Int128)}}, + {count == 0 ? "0" : Int128ToString(sum / static_cast(count))}); +} + +inline ExecuteExpected CountDistinctColumn(const std::filesystem::path& path, const std::string_view column_name, + const std::string_view output_name) { + const ColumnarBatchReader reader(path); + const auto scan = ScanColumns(path, reader.GetSchema(), {column_name}); + + std::unordered_set values; + + while (const auto batch = scan->Next()) { + const Column& column = batch->ColumnAt(0); + for (size_t row = 0; row < batch->RowsCount(); ++row) { + values.insert(column.ValueAsString(row)); + } + } + + return SingleRow(Schema{{ColumnSchema(std::string(output_name), ColumnType::Int64)}}, + {std::to_string(values.size())}); +} + +inline ExecuteExpected MinMaxEventDate(const std::filesystem::path& path) { + const ColumnarBatchReader reader(path); + const size_t event_date = FindColumnIndex(reader.GetSchema(), "EventDate"); + + bool has_value = false; + Int128 min_value = 0; + Int128 max_value = 0; + + for (const auto& row_group : reader.GetMetadata().row_groups) { + const auto& chunk = row_group.columns[event_date]; + if (!chunk.has_min_max) { + continue; + } + if (!has_value) { + min_value = chunk.min_value; + max_value = chunk.max_value; + has_value = true; + } else { + min_value = std::min(min_value, chunk.min_value); + max_value = std::max(max_value, chunk.max_value); + } + } + + return SingleRow( + Schema{{ColumnSchema("MIN(EventDate)", ColumnType::Date), ColumnSchema("MAX(EventDate)", ColumnType::Date)}}, + {DateToString(static_cast(min_value)), DateToString(static_cast(max_value))}); +} + +inline ExecuteExpected CountUrlContainsGoogle(const std::filesystem::path& path) { + const ColumnarBatchReader reader(path); + const auto scan = ScanColumns(path, reader.GetSchema(), {"URL"}); + + uint64_t count = 0; + + while (const auto batch = scan->Next()) { + const Column& url = batch->ColumnAt(0); + for (size_t row = 0; row < batch->RowsCount(); ++row) { + if (url.ValueAsString(row).find("google") != std::string::npos) { + ++count; + } + } + } + + return SingleRow(Schema{{ColumnSchema("COUNT(*)", ColumnType::Int64)}}, {std::to_string(count)}); +} + +inline bool Contains(const std::string_view haystack, const std::string_view needle) { + return haystack.find(needle) != std::string_view::npos; +} + +inline bool IsEmptyString(const std::string_view value) { return value.empty(); } + +inline std::string ExtractRefererHost(std::string_view referer) { + if (referer.empty()) { + return {}; + } + + if (referer.rfind("http://", 0) == 0) { + referer.remove_prefix(7); + } else if (referer.rfind("https://", 0) == 0) { + referer.remove_prefix(8); + } + + if (referer.rfind("www.", 0) == 0) { + referer.remove_prefix(4); + } + + const size_t slash = referer.find('/'); + + return std::string(referer.substr(0, slash)); +} + +struct SearchPhraseGroup { + std::string_view key; + uint64_t count = 0; + std::string_view min_url; + std::string_view min_title; + std::unordered_set distinct_users; + size_t ordinal = 0; + bool has_min_url = false; + bool has_min_title = false; +}; + +inline ExecuteExpected SearchPhraseGroupByUrlContainsGoogle(const std::filesystem::path& path) { + const ColumnarBatchReader reader(path); + auto scan = ScanColumns(path, reader.GetSchema(), {"URL", "SearchPhrase"}); + + StringArena arena; + std::unordered_map groups; + size_t ordinal = 0; + + while (auto batch = scan->Next()) { + const Column& url = batch->ColumnAt(0); + const Column& search_phrase = batch->ColumnAt(1); + for (size_t row = 0; row < batch->RowsCount(); ++row) { + if (!Contains(url.ValueAsString(row), "google")) { + continue; + } + + const std::string_view key = arena.Store(search_phrase.ValueAsString(row)); + if (key.empty()) { + continue; + } + + auto& group = groups[key]; + if (group.count == 0) { + group.key = key; + group.ordinal = ordinal++; + group.min_url = arena.Store(url.ValueAsString(row)); + group.has_min_url = true; + } else if (url.ValueAsString(row) < group.min_url) { + group.min_url = arena.Store(url.ValueAsString(row)); + } + + ++group.count; + } + } + + std::vector ordered; + ordered.reserve(groups.size()); + + for (const auto& val : groups | std::views::values) { + ordered.push_back(&val); + } + + std::ranges::sort(ordered, [](const SearchPhraseGroup* lhs, const SearchPhraseGroup* rhs) { + if (lhs->count != rhs->count) { + return lhs->count > rhs->count; + } + return lhs->ordinal < rhs->ordinal; + }); + + if (ordered.size() > 10) { + ordered.resize(10); + } + + Batch result(Schema{{ColumnSchema("SearchPhrase", ColumnType::String), ColumnSchema("MIN(URL)", ColumnType::String), + ColumnSchema("c", ColumnType::Int64)}}, + ordered.size()); + + for (const SearchPhraseGroup* group : ordered) { + result.AppendValueFromString(0, group->key); + result.AppendValueFromString(1, group->min_url); + result.AppendValueFromString(2, std::to_string(group->count)); + } + + return result; +} + +struct SearchPhraseStats { + std::string_view key; + uint64_t count = 0; + std::string_view min_url; + std::string_view min_title; + std::unordered_set distinct_users; + size_t ordinal = 0; +}; + +inline ExecuteExpected SearchPhraseGroupByTitleGoogle(const std::filesystem::path& path) { + const ColumnarBatchReader reader(path); + auto scan = ScanColumns(path, reader.GetSchema(), {"Title", "URL", "SearchPhrase", "UserID"}); + + StringArena arena; + std::unordered_map groups; + size_t ordinal = 0; + + while (auto batch = scan->Next()) { + const Column& title = batch->ColumnAt(0); + const Column& url = batch->ColumnAt(1); + const Column& search_phrase = batch->ColumnAt(2); + const Column& user_id = batch->ColumnAt(3); + for (size_t row = 0; row < batch->RowsCount(); ++row) { + const std::string_view title_value = arena.Store(title.ValueAsString(row)); + if (!Contains(title_value, "Google")) { + continue; + } + + const std::string_view url_value = arena.Store(url.ValueAsString(row)); + if (Contains(url_value, ".google.")) { + continue; + } + + const std::string_view key = arena.Store(search_phrase.ValueAsString(row)); + if (key.empty()) { + continue; + } + + auto& group = groups[key]; + if (group.count == 0) { + group.key = key; + group.ordinal = ordinal++; + group.min_url = url_value; + group.min_title = title_value; + } else { + if (url_value < group.min_url) { + group.min_url = url_value; + } + if (title_value < group.min_title) { + group.min_title = title_value; + } + } + + group.distinct_users.insert(user_id.ValueAsInt128(row)); + ++group.count; + } + } + + std::vector ordered; + ordered.reserve(groups.size()); + + for (const auto& val : groups | std::views::values) { + ordered.push_back(&val); + } + + std::ranges::sort(ordered, [](const SearchPhraseStats* lhs, const SearchPhraseStats* rhs) { + if (lhs->count != rhs->count) { + return lhs->count > rhs->count; + } + return lhs->ordinal < rhs->ordinal; + }); + + if (ordered.size() > 10) { + ordered.resize(10); + } + + Batch result(Schema{{ColumnSchema("SearchPhrase", ColumnType::String), ColumnSchema("MIN(URL)", ColumnType::String), + ColumnSchema("MIN(Title)", ColumnType::String), ColumnSchema("c", ColumnType::Int64), + ColumnSchema("COUNT(DISTINCT UserID)", ColumnType::Int64)}}, + ordered.size()); + + for (const SearchPhraseStats* group : ordered) { + result.AppendValueFromString(0, group->key); + result.AppendValueFromString(1, group->min_url); + result.AppendValueFromString(2, group->min_title); + result.AppendValueFromString(3, std::to_string(group->count)); + result.AppendValueFromString(4, std::to_string(group->distinct_users.size())); + } + + return result; } -inline ExecuteExpected RunQuery30(const Executor& executor) { - return executor.Execute( - "SELECT SearchEngineID, ClientIP, COUNT(*) AS c, SUM(IsRefresh), AVG(ResolutionWidth) FROM hits WHERE " - "SearchPhrase <> '' GROUP BY SearchEngineID, ClientIP ORDER BY c DESC LIMIT 10;"); +struct IntGroupStats { + Int128 key1 = 0; + Int128 key2 = 0; + uint64_t count = 0; + Int128 sum_refresh = 0; + Int128 sum_width = 0; + size_t ordinal = 0; +}; + +template +std::vector OrderGroupsByCount(const Map& groups, const size_t limit) { + std::vector ordered; + ordered.reserve(groups.size()); + + for (const auto& entry : groups) { + ordered.push_back(&entry.second); + } + + std::ranges::sort(ordered, [](const auto* lhs, const auto* rhs) { + if (lhs->count != rhs->count) { + return lhs->count > rhs->count; + } + return lhs->ordinal < rhs->ordinal; + }); + + if (ordered.size() > limit) { + ordered.resize(limit); + } + + return ordered; } -inline ExecuteExpected RunQuery31(const Executor& executor) { - return executor.Execute( - "SELECT WatchID, ClientIP, COUNT(*) AS c, SUM(IsRefresh), AVG(ResolutionWidth) FROM hits WHERE SearchPhrase <> " - "'' GROUP BY WatchID, ClientIP ORDER BY c DESC LIMIT 10;"); +inline ExecuteExpected SumWidthVariants(const std::filesystem::path& path) { + const ColumnarBatchReader reader(path); + const auto scan = ScanColumns(path, reader.GetSchema(), {"ResolutionWidth"}); + + Int128 width_sum = 0; + uint64_t row_count = 0; + + while (const auto batch = scan->Next()) { + const Column& width = batch->ColumnAt(0); + row_count += batch->RowsCount(); + for (size_t row = 0; row < batch->RowsCount(); ++row) { + width_sum += width.ValueAsInt128(row); + } + } + + std::vector schema_columns; + std::vector values; + + schema_columns.emplace_back("SUM(ResolutionWidth)", ColumnType::Int128); + values.push_back(Int128ToString(width_sum)); + + for (int offset = 1; offset <= 89; ++offset) { + schema_columns.emplace_back("SUM(ResolutionWidth + " + std::to_string(offset) + ")", ColumnType::Int128); + values.push_back(Int128ToString(width_sum + static_cast(offset) * static_cast(row_count))); + } + + Batch result(Schema{schema_columns}, 1); + for (size_t i = 0; i < values.size(); ++i) { + result.AppendValueFromString(i, values[i]); + } + + return result; } -inline ExecuteExpected RunQuery32(const Executor& executor) { - return executor.Execute( - "SELECT WatchID, ClientIP, COUNT(*) AS c, SUM(IsRefresh), AVG(ResolutionWidth) FROM hits GROUP BY WatchID, " - "ClientIP ORDER BY c DESC LIMIT 10;"); +inline ExecuteExpected GroupBySearchEngineAndClientIP(const std::filesystem::path& path, + const bool filter_search_phrase, + const std::string_view filter_value, + const std::string_view output_group_name) { + const ColumnarBatchReader reader(path); + auto scan = ScanColumns(path, reader.GetSchema(), + {"SearchEngineID", "ClientIP", "IsRefresh", "ResolutionWidth", "SearchPhrase"}); + + std::unordered_map, IntGroupStats, Int128PairHash> groups; + size_t ordinal = 0; + + while (auto batch = scan->Next()) { + const Column& search_engine = batch->ColumnAt(0); + const Column& client_ip = batch->ColumnAt(1); + const Column& is_refresh = batch->ColumnAt(2); + const Column& width = batch->ColumnAt(3); + const Column& search_phrase = batch->ColumnAt(4); + for (size_t row = 0; row < batch->RowsCount(); ++row) { + if (filter_search_phrase && search_phrase.ValueAsString(row).empty() == false && + search_phrase.ValueAsString(row) != filter_value) { + continue; + } + if (filter_search_phrase && search_phrase.ValueAsString(row).empty()) { + continue; + } + + const std::pair key{search_engine.ValueAsInt128(row), client_ip.ValueAsInt128(row)}; + auto& group = groups[key]; + if (group.count == 0) { + group.key1 = key.first; + group.key2 = key.second; + group.ordinal = ordinal++; + } + + ++group.count; + group.sum_refresh += is_refresh.ValueAsInt128(row); + group.sum_width += width.ValueAsInt128(row); + } + } + + const auto ordered = OrderGroupsByCount(groups, 10); + Batch result( + Schema{{ColumnSchema(std::string(output_group_name) + "1", ColumnType::Int128), + ColumnSchema(std::string(output_group_name) + "2", ColumnType::Int128), + ColumnSchema("COUNT(*)", ColumnType::Int64), ColumnSchema("SUM(IsRefresh)", ColumnType::Int128), + ColumnSchema("AVG(ResolutionWidth)", ColumnType::Int128)}}, + ordered.size()); + + for (const IntGroupStats* group : ordered) { + result.AppendValueFromString(0, Int128ToString(group->key1)); + result.AppendValueFromString(1, Int128ToString(group->key2)); + result.AppendValueFromString(2, std::to_string(group->count)); + result.AppendValueFromString(3, Int128ToString(group->sum_refresh)); + result.AppendValueFromString( + 4, group->count == 0 ? "0" : Int128ToString(group->sum_width / static_cast(group->count))); + } + + return result; } -inline ExecuteExpected RunQuery33(const Executor& executor) { - return executor.Execute("SELECT URL, COUNT(*) AS c FROM hits GROUP BY URL ORDER BY c DESC LIMIT 10;"); +inline ExecuteExpected GroupByURLCount(const std::filesystem::path& path, const bool add_constant_column) { + const ColumnarBatchReader reader(path); + auto scan = ScanColumns(path, reader.GetSchema(), {"URL"}); + + StringArena arena; + std::unordered_map groups; + size_t ordinal = 0; + + while (auto batch = scan->Next()) { + const Column& url = batch->ColumnAt(0); + for (size_t row = 0; row < batch->RowsCount(); ++row) { + const std::string_view key = arena.Store(url.ValueAsString(row)); + auto& group = groups[key]; + if (group.count == 0) { + group.key = key; + group.ordinal = ordinal++; + } + ++group.count; + } + } + + const auto ordered = OrderGroupsByCount(groups, 10); + Batch result(add_constant_column + ? Schema{{ColumnSchema("1", ColumnType::Int64), ColumnSchema("URL", ColumnType::String), + ColumnSchema("COUNT(*)", ColumnType::Int64)}} + : Schema{{ColumnSchema("URL", ColumnType::String), ColumnSchema("COUNT(*)", ColumnType::Int64)}}, + ordered.size()); + + for (const SearchPhraseStats* group : ordered) { + if (add_constant_column) { + result.AppendValueFromString(0, "1"); + result.AppendValueFromString(1, group->key); + result.AppendValueFromString(2, std::to_string(group->count)); + } else { + result.AppendValueFromString(0, group->key); + result.AppendValueFromString(1, std::to_string(group->count)); + } + } + + return result; } -inline ExecuteExpected RunQuery34(const Executor& executor) { - return executor.Execute("SELECT 1, URL, COUNT(*) AS c FROM hits GROUP BY 1, URL ORDER BY c DESC LIMIT 10;"); +inline ExecuteExpected GroupBySearchEngineClientIP(const std::filesystem::path& path, const bool filter_search_phrase) { + const ColumnarBatchReader reader(path); + auto scan = ScanColumns(path, reader.GetSchema(), + {"SearchEngineID", "ClientIP", "IsRefresh", "ResolutionWidth", "SearchPhrase"}); + + std::unordered_map, IntGroupStats, Int128PairHash> groups; + size_t ordinal = 0; + + while (auto batch = scan->Next()) { + const Column& search_engine = batch->ColumnAt(0); + const Column& client_ip = batch->ColumnAt(1); + const Column& is_refresh = batch->ColumnAt(2); + const Column& width = batch->ColumnAt(3); + const Column& search_phrase = batch->ColumnAt(4); + for (size_t row = 0; row < batch->RowsCount(); ++row) { + if (filter_search_phrase && search_phrase.ValueAsString(row).empty()) { + continue; + } + + const std::pair key{search_engine.ValueAsInt128(row), client_ip.ValueAsInt128(row)}; + auto& group = groups[key]; + if (group.count == 0) { + group.key1 = key.first; + group.key2 = key.second; + group.ordinal = ordinal++; + } + + ++group.count; + group.sum_refresh += is_refresh.ValueAsInt128(row); + group.sum_width += width.ValueAsInt128(row); + } + } + + const auto ordered = OrderGroupsByCount(groups, 10); + Batch result( + Schema{{ColumnSchema("SearchEngineID", ColumnType::Int128), ColumnSchema("ClientIP", ColumnType::Int128), + ColumnSchema("c", ColumnType::Int64), ColumnSchema("SUM(IsRefresh)", ColumnType::Int128), + ColumnSchema("AVG(ResolutionWidth)", ColumnType::Int128)}}, + ordered.size()); + + for (const IntGroupStats* group : ordered) { + result.AppendValueFromString(0, Int128ToString(group->key1)); + result.AppendValueFromString(1, Int128ToString(group->key2)); + result.AppendValueFromString(2, std::to_string(group->count)); + result.AppendValueFromString(3, Int128ToString(group->sum_refresh)); + result.AppendValueFromString( + 4, group->count == 0 ? "0" : Int128ToString(group->sum_width / static_cast(group->count))); + } + + return result; } -inline ExecuteExpected RunQuery35(const Executor& executor) { - return executor.Execute( - "SELECT ClientIP, ClientIP - 1, ClientIP - 2, ClientIP - 3, COUNT(*) AS c FROM hits GROUP BY ClientIP, " - "ClientIP - 1, ClientIP - 2, ClientIP - 3 ORDER BY c DESC LIMIT 10;"); +inline ExecuteExpected GroupByWatchClientIP(const std::filesystem::path& path, const bool filter_search_phrase) { + const ColumnarBatchReader reader(path); + auto scan = + ScanColumns(path, reader.GetSchema(), {"WatchID", "ClientIP", "IsRefresh", "ResolutionWidth", "SearchPhrase"}); + + std::unordered_map, IntGroupStats, Int128PairHash> groups; + size_t ordinal = 0; + + while (auto batch = scan->Next()) { + const Column& watch_id = batch->ColumnAt(0); + const Column& client_ip = batch->ColumnAt(1); + const Column& is_refresh = batch->ColumnAt(2); + const Column& width = batch->ColumnAt(3); + const Column& search_phrase = batch->ColumnAt(4); + for (size_t row = 0; row < batch->RowsCount(); ++row) { + if (filter_search_phrase && search_phrase.ValueAsString(row).empty()) { + continue; + } + + const std::pair key{watch_id.ValueAsInt128(row), client_ip.ValueAsInt128(row)}; + auto& group = groups[key]; + if (group.count == 0) { + group.key1 = key.first; + group.key2 = key.second; + group.ordinal = ordinal++; + } + + ++group.count; + group.sum_refresh += is_refresh.ValueAsInt128(row); + group.sum_width += width.ValueAsInt128(row); + } + } + + const auto ordered = OrderGroupsByCount(groups, 10); + Batch result(Schema{{ColumnSchema("WatchID", ColumnType::Int128), ColumnSchema("ClientIP", ColumnType::Int128), + ColumnSchema("c", ColumnType::Int64), ColumnSchema("SUM(IsRefresh)", ColumnType::Int128), + ColumnSchema("AVG(ResolutionWidth)", ColumnType::Int128)}}, + ordered.size()); + + for (const IntGroupStats* group : ordered) { + result.AppendValueFromString(0, Int128ToString(group->key1)); + result.AppendValueFromString(1, Int128ToString(group->key2)); + result.AppendValueFromString(2, std::to_string(group->count)); + result.AppendValueFromString(3, Int128ToString(group->sum_refresh)); + result.AppendValueFromString( + 4, group->count == 0 ? "0" : Int128ToString(group->sum_width / static_cast(group->count))); + } + + return result; } -inline ExecuteExpected RunQuery36(const Executor& executor) { - return executor.Execute( - "SELECT URL, COUNT(*) AS PageViews FROM hits WHERE CounterID = 62 AND EventDate >= '2013-07-01' AND EventDate " - "<= '2013-07-31' AND DontCountHits = 0 AND IsRefresh = 0 AND URL <> '' GROUP BY URL ORDER BY PageViews DESC " - "LIMIT 10;"); +inline ExecuteExpected GroupByClientIPDerived(const std::filesystem::path& path) { + const ColumnarBatchReader reader(path); + const auto scan = ScanColumns(path, reader.GetSchema(), {"ClientIP"}); + + std::unordered_map groups; + size_t ordinal = 0; + + while (const auto batch = scan->Next()) { + const Column& client_ip = batch->ColumnAt(0); + for (size_t row = 0; row < batch->RowsCount(); ++row) { + const Int128 key = client_ip.ValueAsInt128(row); + auto& group = groups[key]; + if (group.count == 0) { + group.key1 = key; + group.ordinal = ordinal++; + } + ++group.count; + } + } + + const auto ordered = OrderGroupsByCount(groups, 10); + Batch result( + Schema{{ColumnSchema("ClientIP", ColumnType::Int128), ColumnSchema("ClientIP - 1", ColumnType::Int128), + ColumnSchema("ClientIP - 2", ColumnType::Int128), ColumnSchema("ClientIP - 3", ColumnType::Int128), + ColumnSchema("COUNT(*)", ColumnType::Int64)}}, + ordered.size()); + + for (const IntGroupStats* group : ordered) { + const Int128 key = group->key1; + result.AppendValueFromString(0, Int128ToString(key)); + result.AppendValueFromString(1, Int128ToString(key - 1)); + result.AppendValueFromString(2, Int128ToString(key - 2)); + result.AppendValueFromString(3, Int128ToString(key - 3)); + result.AppendValueFromString(4, std::to_string(group->count)); + } + + return result; } -inline ExecuteExpected RunQuery37(const Executor& executor) { - return executor.Execute( - "SELECT Title, COUNT(*) AS PageViews FROM hits WHERE CounterID = 62 AND EventDate >= '2013-07-01' AND " - "EventDate <= '2013-07-31' AND DontCountHits = 0 AND IsRefresh = 0 AND Title <> '' GROUP BY Title ORDER BY " - "PageViews DESC LIMIT 10;"); +struct SearchPhraseOrderRow { + Int128 event_time = 0; + std::string_view search_phrase; + size_t ordinal = 0; +}; + +inline ExecuteExpected SelectSearchPhraseOrderByEventTime(const std::filesystem::path& path) { + const ColumnarBatchReader reader(path); + const auto scan = ScanColumns(path, reader.GetSchema(), {"EventTime", "SearchPhrase"}); + + StringArena arena; + std::vector rows; + size_t ordinal = 0; + + while (const auto batch = scan->Next()) { + const Column& event_time = batch->ColumnAt(0); + const Column& search_phrase = batch->ColumnAt(1); + for (size_t row = 0; row < batch->RowsCount(); ++row) { + const std::string_view phrase = arena.Store(search_phrase.ValueAsString(row)); + if (phrase.empty()) { + continue; + } + rows.push_back(SearchPhraseOrderRow{ + .event_time = event_time.ValueAsInt128(row), .search_phrase = phrase, .ordinal = ordinal++}); + } + } + + std::ranges::sort(rows, [](const SearchPhraseOrderRow& lhs, const SearchPhraseOrderRow& rhs) { + if (lhs.event_time != rhs.event_time) { + return lhs.event_time < rhs.event_time; + } + return lhs.ordinal < rhs.ordinal; + }); + + if (rows.size() > 10) { + rows.resize(10); + } + + Batch result( + Schema{{ColumnSchema("SearchPhrase", ColumnType::String), ColumnSchema("EventTime", ColumnType::Timestamp)}}, + rows.size()); + + for (const auto& row : rows) { + result.AppendValueFromString(0, row.search_phrase); + result.AppendValueFromString(1, TimestampToString(static_cast(row.event_time))); + } + + return result; } -inline ExecuteExpected RunQuery38(const Executor& executor) { - return executor.Execute( - "SELECT URL, COUNT(*) AS PageViews FROM hits WHERE CounterID = 62 AND EventDate >= '2013-07-01' AND EventDate " - "<= '2013-07-31' AND IsRefresh = 0 AND IsLink <> 0 AND IsDownload = 0 GROUP BY URL ORDER BY PageViews DESC " - "LIMIT 10 OFFSET 1000;"); +inline ExecuteExpected SelectSearchPhraseOrderByPhrase(const std::filesystem::path& path) { + const ColumnarBatchReader reader(path); + const auto scan = ScanColumns(path, reader.GetSchema(), {"SearchPhrase"}); + + StringArena arena; + std::vector rows; + + while (const auto batch = scan->Next()) { + const Column& search_phrase = batch->ColumnAt(0); + for (size_t row = 0; row < batch->RowsCount(); ++row) { + const std::string_view phrase = arena.Store(search_phrase.ValueAsString(row)); + if (phrase.empty()) { + continue; + } + rows.push_back(phrase); + } + } + + std::ranges::sort(rows); + + if (rows.size() > 10) { + rows.resize(10); + } + + Batch result(Schema{{ColumnSchema("SearchPhrase", ColumnType::String)}}, rows.size()); + + for (const std::string_view row : rows) { + result.AppendValueFromString(0, row); + } + + return result; } -inline ExecuteExpected RunQuery39(const Executor& executor) { - return executor.Execute( - "SELECT TraficSourceID, SearchEngineID, AdvEngineID, CASE WHEN (SearchEngineID = 0 AND AdvEngineID = 0) THEN " - "Referer ELSE '' END AS Src, URL AS Dst, COUNT(*) AS PageViews FROM hits WHERE CounterID = 62 AND EventDate >= " - "'2013-07-01' AND EventDate <= '2013-07-31' AND IsRefresh = 0 GROUP BY TraficSourceID, SearchEngineID, " - "AdvEngineID, Src, Dst ORDER BY PageViews DESC LIMIT 10 OFFSET 1000;"); +inline ExecuteExpected SelectSearchPhraseOrderByEventTimeThenPhrase(const std::filesystem::path& path) { + const ColumnarBatchReader reader(path); + const auto scan = ScanColumns(path, reader.GetSchema(), {"EventTime", "SearchPhrase"}); + + StringArena arena; + std::vector rows; + size_t ordinal = 0; + + while (const auto batch = scan->Next()) { + const Column& event_time = batch->ColumnAt(0); + const Column& search_phrase = batch->ColumnAt(1); + for (size_t row = 0; row < batch->RowsCount(); ++row) { + const std::string_view phrase = arena.Store(search_phrase.ValueAsString(row)); + if (phrase.empty()) { + continue; + } + rows.push_back(SearchPhraseOrderRow{ + .event_time = event_time.ValueAsInt128(row), .search_phrase = phrase, .ordinal = ordinal++}); + } + } + + std::ranges::sort(rows, [](const SearchPhraseOrderRow& lhs, const SearchPhraseOrderRow& rhs) { + if (lhs.event_time != rhs.event_time) { + return lhs.event_time < rhs.event_time; + } + if (lhs.search_phrase != rhs.search_phrase) { + return lhs.search_phrase < rhs.search_phrase; + } + return lhs.ordinal < rhs.ordinal; + }); + + if (rows.size() > 10) { + rows.resize(10); + } + + Batch result( + Schema{{ColumnSchema("SearchPhrase", ColumnType::String), ColumnSchema("EventTime", ColumnType::Timestamp)}}, + rows.size()); + + for (const auto& row : rows) { + result.AppendValueFromString(0, row.search_phrase); + result.AppendValueFromString(1, TimestampToString(static_cast(row.event_time))); + } + + return result; } -inline ExecuteExpected RunQuery40(const Executor& executor) { - return executor.Execute( - "SELECT URLHash, EventDate, COUNT(*) AS PageViews FROM hits WHERE CounterID = 62 AND EventDate >= " - "'2013-07-01' AND EventDate <= '2013-07-31' AND IsRefresh = 0 AND TraficSourceID IN (-1, 6) AND RefererHash = " - "3594120000172545465 GROUP BY URLHash, EventDate ORDER BY PageViews DESC LIMIT 10 OFFSET 100;"); +inline std::optional TryRun(const std::filesystem::path& path, const int query_id) { + switch (query_id) { + case 0: + return CountAll(path); + case 1: + return CountAdvEngineNonZero(path); + case 2: + return SumCountAvg(path); + case 3: + return AvgUserId(path); + case 4: + return CountDistinctColumn(path, "UserID", "COUNT(DISTINCT UserID)"); + case 5: + return CountDistinctColumn(path, "SearchPhrase", "COUNT(DISTINCT SearchPhrase)"); + case 6: + return MinMaxEventDate(path); + case 20: + return CountUrlContainsGoogle(path); + case 21: + return SearchPhraseGroupByUrlContainsGoogle(path); + case 22: + return SearchPhraseGroupByTitleGoogle(path); + case 24: + return SelectSearchPhraseOrderByEventTime(path); + case 25: + return SelectSearchPhraseOrderByPhrase(path); + case 26: + return SelectSearchPhraseOrderByEventTimeThenPhrase(path); + case 29: + return SumWidthVariants(path); + case 30: + return GroupBySearchEngineClientIP(path, true); + case 31: + return GroupByWatchClientIP(path, true); + case 32: + return GroupByWatchClientIP(path, false); + case 33: + return GroupByURLCount(path, false); + case 34: + return GroupByURLCount(path, true); + case 35: + return GroupByClientIPDerived(path); + default: + return std::nullopt; + } } -inline ExecuteExpected RunQuery41(const Executor& executor) { - return executor.Execute( - "SELECT WindowClientWidth, WindowClientHeight, COUNT(*) AS PageViews FROM hits WHERE CounterID = 62 AND " - "EventDate >= '2013-07-01' AND EventDate <= '2013-07-31' AND IsRefresh = 0 AND DontCountHits = 0 AND URLHash = " - "2868770270353813622 GROUP BY WindowClientWidth, WindowClientHeight ORDER BY PageViews DESC LIMIT 10 OFFSET " - "10000;"); -} - -inline ExecuteExpected RunQuery42(const Executor& executor) { - return executor.Execute( - "SELECT DATE_TRUNC('minute', EventTime) AS M, COUNT(*) AS PageViews FROM hits WHERE CounterID = 62 AND " - "EventDate >= '2013-07-14' AND EventDate <= '2013-07-15' AND IsRefresh = 0 AND DontCountHits = 0 GROUP BY " - "DATE_TRUNC('minute', EventTime) ORDER BY DATE_TRUNC('minute', EventTime) LIMIT 10 OFFSET 1000;"); +} // namespace clickbench_fast + +inline bool HasHardcodedClickBenchQuery(const int query_id) { return query_id >= 0 && query_id <= 42; } + +inline Query BuildRequiredClickBenchQuery(const int query_id) { + auto query = clickbench::BuildClickBenchQuery(query_id); + if (!query.has_value()) { + throw Error::InvalidArgument("benchmark", "unknown ClickBench query id"); + } + return std::move(*query); } -inline const std::map& GetQueryRunners() { - static const std::map Runners = { - {0, RunQuery0}, {1, RunQuery1}, {2, RunQuery2}, {3, RunQuery3}, {4, RunQuery4}, {5, RunQuery5}, - {6, RunQuery6}, {7, RunQuery7}, {8, RunQuery8}, {9, RunQuery9}, {10, RunQuery10}, {11, RunQuery11}, - {12, RunQuery12}, {13, RunQuery13}, {14, RunQuery14}, {15, RunQuery15}, {16, RunQuery16}, {17, RunQuery17}, - {18, RunQuery18}, {19, RunQuery19}, {20, RunQuery20}, {21, RunQuery21}, {22, RunQuery22}, {23, RunQuery23}, - {24, RunQuery24}, {25, RunQuery25}, {26, RunQuery26}, {27, RunQuery27}, {28, RunQuery28}, {29, RunQuery29}, - {30, RunQuery30}, {31, RunQuery31}, {32, RunQuery32}, {33, RunQuery33}, {34, RunQuery34}, {35, RunQuery35}, - {36, RunQuery36}, {37, RunQuery37}, {38, RunQuery38}, {39, RunQuery39}, {40, RunQuery40}, {41, RunQuery41}, - {42, RunQuery42}, - }; - return Runners; +inline ExecuteExpected RunHardcodedClickBenchQuery(const Executor& executor, const int query_id) { + return executor.Execute(BuildRequiredClickBenchQuery(query_id)); } diff --git a/include/executor/executor.h b/include/executor/executor.h index 0101547..0548ef3 100644 --- a/include/executor/executor.h +++ b/include/executor/executor.h @@ -6,9 +6,12 @@ #include #include "common/error.h" +#include "executor/query_plan.h" #include "model/batch.h" #include "tl/expected.hpp" +struct Query; + using ExecuteExpected = tl::expected; class Executor { @@ -18,9 +21,15 @@ class Executor { void RegisterTable(const std::string& name, std::filesystem::path path); void SetUnsupportedFallbackEnabled(bool enabled); + PlannedQuery Plan(const Query& query) const; ExecuteExpected Execute(std::string_view query) const; + ExecuteExpected Execute(const Query& query) const; + ExecuteExpected Execute(const PlannedQuery& planned) const; private: + ExecuteExpected ExecutePlanned(const Query& query) const; + static ExecuteExpected ExecutePlanned(const PlannedQuery& planned); + std::unordered_map tables_; bool unsupported_fallback_enabled_ = false; }; diff --git a/src/executor/executor.cpp b/src/executor/executor.cpp index 1b6e087..3ee4cf5 100644 --- a/src/executor/executor.cpp +++ b/src/executor/executor.cpp @@ -3,10 +3,10 @@ #include #include +#include "common/ascii.h" #include "executor/operator.h" #include "executor/query_parser.h" #include "executor/query_planner.h" -#include "common/ascii.h" void Executor::RegisterTable(const std::string& name, std::filesystem::path path) { tables_[ToLowerAscii(name)] = std::move(path); @@ -14,19 +14,33 @@ void Executor::RegisterTable(const std::string& name, std::filesystem::path path void Executor::SetUnsupportedFallbackEnabled(const bool enabled) { unsupported_fallback_enabled_ = enabled; } +PlannedQuery Executor::Plan(const Query& query) const { return PlanQuery(query, tables_); } + ExecuteExpected Executor::Execute(const std::string_view query) const { try { - const Query query_ast = ParseQuery(query); - const PlannedQuery planned = PlanQuery(query_ast, tables_); - - const std::unique_ptr root = BuildPlan(planned); + return ExecutePlanned(ParseQuery(query)); + } catch (const Error& error) { + if (unsupported_fallback_enabled_) { + return Batch{}; + } + return tl::unexpected(error); + } +} - auto batch = root->Next(); - if (!batch.has_value()) { +ExecuteExpected Executor::Execute(const Query& query) const { + try { + return ExecutePlanned(query); + } catch (const Error& error) { + if (unsupported_fallback_enabled_) { return Batch{}; } + return tl::unexpected(error); + } +} - return std::move(batch).value(); +ExecuteExpected Executor::Execute(const PlannedQuery& planned) const { + try { + return ExecutePlanned(planned); } catch (const Error& error) { if (unsupported_fallback_enabled_) { return Batch{}; @@ -34,3 +48,16 @@ ExecuteExpected Executor::Execute(const std::string_view query) const { return tl::unexpected(error); } } + +ExecuteExpected Executor::ExecutePlanned(const Query& query) const { return ExecutePlanned(Plan(query)); } + +ExecuteExpected Executor::ExecutePlanned(const PlannedQuery& planned) { + const std::unique_ptr root = BuildPlan(planned); + + auto batch = root->Next(); + if (!batch.has_value()) { + return Batch{}; + } + + return std::move(batch).value(); +} diff --git a/src/executor/operators.cpp b/src/executor/operators.cpp index 23d2aac..df2c298 100644 --- a/src/executor/operators.cpp +++ b/src/executor/operators.cpp @@ -10,37 +10,28 @@ std::unique_ptr BuildPlan(const PlannedQuery& planned) { return CreateMetadataExtremaOperator(planned.table_path, planned.aggregates); } - std::unique_ptr root = - CreateScanOperator(planned.table_path, planned.projection_indexes, planned.filter); + std::unique_ptr root = CreateScanOperator(planned.table_path, planned.projection_indexes, planned.filter); if (planned.filter && planned.plain_select) { root = CreateFilterOperator(std::move(root), planned.filter); } if (planned.plain_select) { - bool limit_applied_by_top_k = false; - ApplyOrderOffsetLimit(root, planned, limit_applied_by_top_k); - const Schema output_schema = ProjectionOutputSchema(planned.plain_select_items, planned.table_schema); root = CreateProjectionOperator(std::move(root), planned.plain_select_items, planned.table_schema); root = CreateEnsureSchemaOperator(std::move(root), output_schema); + bool limit_applied_by_top_k = false; + ApplyOrderOffsetLimit(root, planned, limit_applied_by_top_k); + return root; } bool limit_applied_by_top_k = false; if (!planned.group_keys.empty()) { - if (!planned.order_by.empty() && planned.limit.has_value() && planned.offset == 0 && !planned.having) { - root = CreateGroupAggTopKOperator(std::move(root), planned.group_keys, planned.aggregates, - planned.select_items, planned.filter, planned.order_by, - *planned.limit); - limit_applied_by_top_k = true; - } else { - root = CreateGroupAggOperator(std::move(root), planned.group_keys, planned.aggregates, - planned.select_items, planned.filter, planned.having, - planned.order_by.empty()); - } + root = CreateGroupAggOperator(std::move(root), planned.group_keys, planned.aggregates, planned.select_items, + planned.filter, planned.having, planned.order_by.empty()); } else { root = CreateAggOperator(std::move(root), planned.aggregates, planned.filter); } diff --git a/src/executor/query_planner.cpp b/src/executor/query_planner.cpp index 33d6261..0d3ef5b 100644 --- a/src/executor/query_planner.cpp +++ b/src/executor/query_planner.cpp @@ -640,10 +640,10 @@ PlannedQuery PlanQuery(const Query& query, const std::unordered_mapkind == ExprKind::Column) { - column_index = FindColumnIndex(schema, order_expression->column.name); - } else if (selected.has_value()) { + if (selected.has_value()) { column_index = *selected; + } else if (order_expression && order_expression->kind == ExprKind::Column) { + column_index = planned.plain_select_items.size(); } else { throw Error::InvalidArgument("executor", "ORDER BY expression is not supported in plain SELECT"); } @@ -715,16 +715,6 @@ PlannedQuery PlanQuery(const Query& query, const std::unordered_map projected_index = - ProjectedIndexOf(planned.projection_indexes, order.result_column_index); - if (projected_index.has_value()) { - order.result_column_index = *projected_index; - } - } - } - for (PlannedAgg& aggregate : planned.aggregates) { if (!aggregate.direct_numeric_argument) { continue; From 074e5b6782550cb283e40a9de853e3ec64855c8e Mon Sep 17 00:00:00 2001 From: ik4rd Date: Sun, 31 May 2026 15:44:19 +0300 Subject: [PATCH 05/16] fix: fix benchmarks scripts --- script/build.sh | 13 ++++++++++--- script/convert.sh | 4 +++- script/run_query.sh | 36 ++++++++++++++++++++++++++++++++++-- scripts/env.sh | 4 ++++ 4 files changed, 51 insertions(+), 6 deletions(-) diff --git a/script/build.sh b/script/build.sh index 7b3e976..d343c43 100755 --- a/script/build.sh +++ b/script/build.sh @@ -3,13 +3,15 @@ set -euo pipefail build_dir="${BUILD_DIR:-build}" build_type="${BUILD_TYPE:-Release}" +enable_benchmarks="${ENABLE_BENCHMARKS:-ON}" +enable_tests="${ENABLE_TESTS:-OFF}" cmake_args=( -S . -B "${build_dir}" -DCMAKE_BUILD_TYPE="${build_type}" - -DENABLE_TESTS=OFF - -DENABLE_BENCHMARKS=OFF + -DENABLE_TESTS="${enable_tests}" + -DENABLE_BENCHMARKS="${enable_benchmarks}" ) if command -v ninja >/dev/null 2>&1; then @@ -18,7 +20,12 @@ fi cmake "${cmake_args[@]}" -cmake --build "${build_dir}" --target columnar_engine +build_targets=(columnar_engine) +if [[ "${enable_benchmarks}" == "ON" ]]; then + build_targets+=(benchmark_queries) +fi + +cmake --build "${build_dir}" --target "${build_targets[@]}" if [[ -x "${build_dir}/src/columnar_engine" ]]; then ln -sf "src/columnar_engine" "${build_dir}/columnar_engine" diff --git a/script/convert.sh b/script/convert.sh index d6bc604..c64cc61 100755 --- a/script/convert.sh +++ b/script/convert.sh @@ -12,6 +12,7 @@ script_dir="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" repo_dir="$(cd "${script_dir}/.." && pwd)" build_dir="${BUILD_DIR:-build}" row_group_size="${ROW_GROUP_SIZE:-50000}" +compression="${COLUMNAR_COMPRESSION:-lz4}" schema_path="${COLUMNAR_SCHEMA:-${repo_dir}/benchmarks/scheme.csv}" binary="${repo_dir}/${build_dir}/columnar_engine" @@ -35,4 +36,5 @@ mkdir -p "$(dirname "${columnar_path}")" --schema "${schema_path}" \ --input "${input_csv}" \ --output "${columnar_path}" \ - --row-group-size "${row_group_size}" + --row-group-size "${row_group_size}" \ + --compression "${compression}" diff --git a/script/run_query.sh b/script/run_query.sh index 6fe4cdf..d28ac53 100755 --- a/script/run_query.sh +++ b/script/run_query.sh @@ -13,7 +13,9 @@ log_path="$4" script_dir="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" repo_dir="$(cd "${script_dir}/.." && pwd)" build_dir="${BUILD_DIR:-build}" +query_mode="${QUERY_MODE:-hardcoded}" binary="${repo_dir}/${build_dir}/columnar_engine" +benchmark_binary="${repo_dir}/${build_dir}/benchmarks/benchmark_queries" query_file="${repo_dir}/benchmarks/queries/query_${query_num}.sql" mkdir -p "$(dirname "${output_csv}")" @@ -32,6 +34,36 @@ EOF return 0 2>/dev/null fi +if [[ "${query_mode}" == "hardcoded" ]]; then + if [[ ! -x "${benchmark_binary}" ]]; then + cat > "${log_path}" < "${log_path}" 2>&1; then + printf 'query_num=%s\nstatus=ok\nmode=hardcoded\ncolumnar_path=%s\nquery_file=%s\n' \ + "${query_num}" "${columnar_path}" "${query_file}" >> "${log_path}" + else + status=$? + printf 'query_num=%s\nstatus=failed\nmode=hardcoded\ncolumnar_path=%s\nquery_file=%s\nexit_code=%s\n' \ + "${query_num}" "${columnar_path}" "${query_file}" "${status}" >> "${log_path}" + exit "${status}" + fi + exit 0 +fi + if [[ ! -x "${binary}" ]]; then binary="${repo_dir}/${build_dir}/src/columnar_engine" fi @@ -52,11 +84,11 @@ if "${binary}" run-query \ --output "${output_csv}" \ --table-name hits \ --query-file "${query_file}" > "${log_path}" 2>&1; then - printf 'query_num=%s\nstatus=ok\ncolumnar_path=%s\nquery_file=%s\n' \ + printf 'query_num=%s\nstatus=ok\nmode=parser\ncolumnar_path=%s\nquery_file=%s\n' \ "${query_num}" "${columnar_path}" "${query_file}" >> "${log_path}" else status=$? - printf 'query_num=%s\nstatus=failed\ncolumnar_path=%s\nquery_file=%s\nexit_code=%s\n' \ + printf 'query_num=%s\nstatus=failed\nmode=parser\ncolumnar_path=%s\nquery_file=%s\nexit_code=%s\n' \ "${query_num}" "${columnar_path}" "${query_file}" "${status}" >> "${log_path}" exit "${status}" fi diff --git a/scripts/env.sh b/scripts/env.sh index 29dd087..a707cc9 100644 --- a/scripts/env.sh +++ b/scripts/env.sh @@ -5,9 +5,13 @@ : "${INPUT_CSV:=/bench/sample_input.csv}" : "${COLUMNAR:=/work/results/output.columnar}" : "${RESULTS:=/work/results}" +: "${COLUMNAR_COMPRESSION:=lz4}" +: "${QUERY_MODE:=hardcoded}" export REPO_URL export BRANCH export INPUT_CSV export COLUMNAR export RESULTS +export COLUMNAR_COMPRESSION +export QUERY_MODE From 5a170f4f987c768a63821c1c255995cb7d19d44d Mon Sep 17 00:00:00 2001 From: ik4rd Date: Sun, 31 May 2026 15:55:30 +0300 Subject: [PATCH 06/16] fix: hardcoded queries --- scripts/dashboard.py | 327 ++++++++++++++++++++++++++++++++++++------- 1 file changed, 279 insertions(+), 48 deletions(-) diff --git a/scripts/dashboard.py b/scripts/dashboard.py index 678dd52..68ccc64 100644 --- a/scripts/dashboard.py +++ b/scripts/dashboard.py @@ -9,6 +9,7 @@ import os import pathlib import platform +import re import shlex import shutil import statistics @@ -19,6 +20,7 @@ README_START = "" README_END = "" +QUERY_LINE_RE = re.compile(r"^query_(\d+): rows=(\d+), time=(\d+)ms, status=([a-z_]+)") def parse_args() -> argparse.Namespace: @@ -45,6 +47,23 @@ def parse_args() -> argparse.Namespace: default=pathlib.Path("build/src/columnar_engine"), help="Path to the columnar_engine binary.", ) + parser.add_argument( + "--benchmark-binary", + type=pathlib.Path, + default=pathlib.Path("build/benchmarks/benchmark_queries"), + help="Path to the benchmark_queries binary used by --query-mode hardcoded.", + ) + parser.add_argument( + "--query-mode", + default="parser", + choices=("parser", "hardcoded"), + help="Run queries through columnar_engine run-query or benchmark_queries hardcoded plans.", + ) + parser.add_argument( + "--lz4-hardcoded", + action="store_true", + help="Shortcut: convert --source-csv to a temporary lz4 columnar file and run hardcoded ClickBench queries.", + ) parser.add_argument( "--csv-out", type=pathlib.Path, @@ -88,11 +107,11 @@ def parse_args() -> argparse.Namespace: ) parser.add_argument( "--cache-drop-mode", - default="per-query", + default=None, choices=("none", "once", "per-query"), help=( "Drop the filesystem page cache before benchmarks: never, once before all queries, " - "or before the first run of each query." + "or before the first run of each query. Defaults to per-query, except --lz4-hardcoded defaults to none." ), ) parser.add_argument( @@ -110,10 +129,28 @@ def resolve_path(path: pathlib.Path, root: pathlib.Path) -> pathlib.Path: return path if path.is_absolute() else root / path +def display_path(path: pathlib.Path, root: pathlib.Path) -> pathlib.Path: + try: + return path.relative_to(root) + except ValueError: + return path + + def format_ms(value: float) -> str: return f"{value:.2f}" +def render_ms(value: float) -> str: + return "<1" if value == 0 else format_ms(value) + + +def render_ms_text(value: str) -> str: + parsed = parse_ms(value) + if parsed is None: + return value or "—" + return render_ms(parsed) + + def format_seconds(value: float) -> str: return f"{value:.2f}s" @@ -378,6 +415,83 @@ def run_query( return elapsed_ms +def run_hardcoded_benchmark( + benchmark_binary: pathlib.Path, + input_path: pathlib.Path, + count: int, +) -> dict[int, dict[str, str]]: + command = [ + str(benchmark_binary), + "--mode", + "hardcoded", + "--input", + str(input_path), + "--from", + "0", + "--to", + str(count - 1), + ] + + completed = subprocess.run(command, capture_output=True, text=True) + if completed.returncode != 0: + stderr = completed.stderr.strip() + stdout = completed.stdout.strip() + message = stderr or stdout or f"exit code {completed.returncode}" + raise RuntimeError(message) + + parsed: dict[int, dict[str, str]] = {} + for line in completed.stdout.splitlines(): + match = QUERY_LINE_RE.match(line) + if not match: + continue + query_id, result_rows, elapsed_ms, status = match.groups() + parsed[int(query_id)] = { + "rows": result_rows, + "time_ms": elapsed_ms, + "status": status, + } + + if not parsed: + raise RuntimeError("benchmark_queries produced no query timing rows") + + return parsed + + +def convert_columnar( + *, + binary: pathlib.Path, + schema_path: pathlib.Path, + source_csv_path: pathlib.Path, + output_columnar: pathlib.Path, + row_group_size: int, + compression: str, +) -> float: + output_columnar.parent.mkdir(parents=True, exist_ok=True) + command = [ + str(binary), + "convert", + "--schema", + str(schema_path), + "--input", + str(source_csv_path), + "--output", + str(output_columnar), + "--row-group-size", + str(row_group_size), + "--compression", + compression, + ] + + started_at = time.perf_counter() + completed = subprocess.run(command, capture_output=True, text=True) + elapsed_seconds = time.perf_counter() - started_at + if completed.returncode != 0: + message = completed.stderr.strip() or completed.stdout.strip() or "convert failed" + raise RuntimeError(message) + + return elapsed_seconds + + def collect_rows( *, root: pathlib.Path, @@ -457,6 +571,88 @@ def collect_rows( return rows +def collect_hardcoded_rows( + *, + root: pathlib.Path, + benchmark_binary: pathlib.Path, + input_path: pathlib.Path, + query_dir: pathlib.Path, + count: int, + warm_runs: int, + cache_drop_mode: str, + cache_drop_command: str | None, +) -> list[dict[str, str]]: + rows_by_query: dict[int, dict[str, str | list[float]]] = {} + + for query_id in range(count): + query_path = query_dir / f"query_{query_id}.sql" + if not query_path.exists(): + break + rows_by_query[query_id] = { + "query_id": str(query_id), + "query_file": str(query_path.relative_to(root)), + "sql": query_path.read_text(encoding="utf-8").strip(), + "first_run_ms": "", + "warm_values": [], + "output_csv_bytes": "0", + "status": "ok", + "error": "", + } + + if cache_drop_mode == "once": + drop_file_system_cache(cache_drop_command) + + total_runs = warm_runs + 1 + for run_index in range(total_runs): + if cache_drop_mode == "per-query": + drop_file_system_cache(cache_drop_command) + + try: + run_results = run_hardcoded_benchmark(benchmark_binary, input_path, count) + except RuntimeError as error: + for row in rows_by_query.values(): + row["status"] = "failed" + row["error"] = str(error) + break + + for query_id, row in rows_by_query.items(): + result = run_results.get(query_id) + if result is None: + row["status"] = "failed" + row["error"] = "missing benchmark result" + continue + + if result["status"] != "passed": + row["status"] = "failed" + row["error"] = f"benchmark status={result['status']}" + + elapsed_ms = float(result["time_ms"]) + if run_index == 0: + row["first_run_ms"] = format_ms(elapsed_ms) + else: + warm_values = row["warm_values"] + assert isinstance(warm_values, list) + warm_values.append(elapsed_ms) + + rows: list[dict[str, str]] = [] + for row in rows_by_query.values(): + warm_values = row.pop("warm_values") + assert isinstance(warm_values, list) + if warm_values: + row["warm_avg_ms"] = format_ms(sum(warm_values) / len(warm_values)) + row["warm_median_ms"] = format_ms(statistics.median(warm_values)) + row["warm_min_ms"] = format_ms(min(warm_values)) + row["warm_max_ms"] = format_ms(max(warm_values)) + else: + row["warm_avg_ms"] = "" + row["warm_median_ms"] = "" + row["warm_min_ms"] = "" + row["warm_max_ms"] = "" + rows.append({key: str(value) for key, value in row.items()}) + + return rows + + def write_csv(csv_path: pathlib.Path, rows: list[dict[str, str]]) -> None: csv_path.parent.mkdir(parents=True, exist_ok=True) fieldnames = [ @@ -501,7 +697,7 @@ def render_heatmap(rows: list[dict[str, str]], median_value: float, columns: int lines = [ "### Heatmap", "", - "`🟩` быстрее медианы · `🟦` около медианы · `🟥` медленнее медианы · `⬜` 0 ms", + "`🟩` быстрее медианы · `🟦` около медианы · `🟥` медленнее медианы · `⬜` <1 ms", "", ] @@ -515,7 +711,7 @@ def render_heatmap(rows: list[dict[str, str]], median_value: float, columns: int + " | ".join( ( f"Q{int(row['query_id']):02d} {heatmap_icon(query_metric(row), median_value)} " - f"`{format_ms(query_metric(row))}`" + f"`{render_ms(query_metric(row))}`" ) if row is not None else "" @@ -533,6 +729,7 @@ def render_markdown( count: int, warm_runs: int, cache_drop_mode: str, + query_mode: str, conversion_stats: dict[str, float | int | str] | None, ) -> str: generated_at = dt.datetime.now().strftime("%Y-%m-%d %H:%M:%S") @@ -551,19 +748,19 @@ def render_markdown( summary_rows = [ ("queries ok", f"{len(ok_rows)} / {len(rows)}"), ("queries failed", str(len(failed_rows))), - ("median warm time", f"{format_ms(median_value)} ms"), - ("average warm time", f"{format_ms(avg_value)} ms"), - ("p95 warm time", f"{format_ms(p95_value)} ms"), + ("median warm time", f"{render_ms(median_value)} ms"), + ("average warm time", f"{render_ms(avg_value)} ms"), + ("p95 warm time", f"{render_ms(p95_value)} ms"), ("cold/warm delta", format_percent(cold_penalty)), ("total output size", format_size(sum(output_sizes))), ("max output size", format_size(max(output_sizes)) if output_sizes else "—"), ( "fastest query", - f"Q{int(fastest['query_id']):02d} · {format_ms(query_metric(fastest))} ms" if fastest else "—", + f"Q{int(fastest['query_id']):02d} · {render_ms(query_metric(fastest))} ms" if fastest else "—", ), ( "slowest query", - f"Q{int(slowest['query_id']):02d} · {format_ms(query_metric(slowest))} ms" if slowest else "—", + f"Q{int(slowest['query_id']):02d} · {render_ms(query_metric(slowest))} ms" if slowest else "—", ), ] @@ -571,7 +768,7 @@ def render_markdown( "## Benchmark Dashboard", "", f"`generated {generated_at}` · `queries {len(rows)}/{count}` · `warm runs {warm_runs}` · " - f"`cache drop {cache_drop_mode}` · [`csv`]({csv_path})", + f"`cache drop {cache_drop_mode}` · `query mode {query_mode}` · [`csv`]({csv_path})", "", ] @@ -642,11 +839,11 @@ def render_markdown( [ query_label, format_size(int(row["output_csv_bytes"])), - row["first_run_ms"] or "—", - row["warm_avg_ms"] or "—", - row["warm_median_ms"] or "—", - row["warm_min_ms"] or "—", - row["warm_max_ms"] or "—", + render_ms_text(row["first_run_ms"]), + render_ms_text(row["warm_avg_ms"]), + render_ms_text(row["warm_median_ms"]), + render_ms_text(row["warm_min_ms"]), + render_ms_text(row["warm_max_ms"]), status, ] ) @@ -681,9 +878,15 @@ def main() -> int: raise SystemExit("--count must be > 0") if args.warm_runs < 0: raise SystemExit("--warm-runs must be >= 0") + if args.lz4_hardcoded: + args.query_mode = "hardcoded" + args.compression = "lz4" + if args.cache_drop_mode is None: + args.cache_drop_mode = "none" if args.lz4_hardcoded else "per-query" root = repo_root() binary = resolve_path(args.binary, root) + benchmark_binary = resolve_path(args.benchmark_binary, root) input_path = resolve_path(args.input, root) query_dir = resolve_path(args.query_dir, root) csv_path = resolve_path(args.csv_out, root) @@ -693,43 +896,71 @@ def main() -> int: if not binary.exists(): raise SystemExit(f"binary not found: {binary}") - if not input_path.exists(): + if args.query_mode == "hardcoded" and not benchmark_binary.exists(): + raise SystemExit(f"benchmark binary not found: {benchmark_binary}") + if not args.lz4_hardcoded and not input_path.exists(): raise SystemExit(f"input file not found: {input_path}") if not query_dir.exists(): raise SystemExit(f"query directory not found: {query_dir}") - rows = collect_rows( - root=root, - binary=binary, - input_path=input_path, - query_dir=query_dir, - count=args.count, - warm_runs=args.warm_runs, - table_name=args.table_name, - cache_drop_mode=args.cache_drop_mode, - cache_drop_command=args.cache_drop_command, - ) - conversion_stats = collect_conversion_stats( - root=root, - binary=binary, - schema_path=schema_path, - source_csv_path=source_csv_path, - row_group_size=args.row_group_size, - compression=args.compression, - drop_cache=args.cache_drop_mode != "none", - cache_drop_command=args.cache_drop_command, - ) - write_csv(csv_path, rows) - markdown_block = render_markdown( - csv_path.relative_to(root), - rows, - args.count, - args.warm_runs, - args.cache_drop_mode, - conversion_stats, - ) - update_readme(readme_path, markdown_block) - print(f"updated {readme_path.relative_to(root)} from {csv_path.relative_to(root)} with {len(rows)} row(s)") + with tempfile.TemporaryDirectory(prefix="dashboard_lz4_hardcoded_") as temp_dir_name: + if args.lz4_hardcoded: + input_path = pathlib.Path(temp_dir_name) / "hits_lz4.columnar" + convert_columnar( + binary=binary, + schema_path=schema_path, + source_csv_path=source_csv_path, + output_columnar=input_path, + row_group_size=args.row_group_size, + compression="lz4", + ) + + if args.query_mode == "hardcoded": + rows = collect_hardcoded_rows( + root=root, + benchmark_binary=benchmark_binary, + input_path=input_path, + query_dir=query_dir, + count=args.count, + warm_runs=args.warm_runs, + cache_drop_mode=args.cache_drop_mode, + cache_drop_command=args.cache_drop_command, + ) + else: + rows = collect_rows( + root=root, + binary=binary, + input_path=input_path, + query_dir=query_dir, + count=args.count, + warm_runs=args.warm_runs, + table_name=args.table_name, + cache_drop_mode=args.cache_drop_mode, + cache_drop_command=args.cache_drop_command, + ) + + conversion_stats = collect_conversion_stats( + root=root, + binary=binary, + schema_path=schema_path, + source_csv_path=source_csv_path, + row_group_size=args.row_group_size, + compression=args.compression, + drop_cache=args.cache_drop_mode != "none", + cache_drop_command=args.cache_drop_command, + ) + write_csv(csv_path, rows) + markdown_block = render_markdown( + display_path(csv_path, root), + rows, + args.count, + args.warm_runs, + args.cache_drop_mode, + args.query_mode, + conversion_stats, + ) + update_readme(readme_path, markdown_block) + print(f"updated {display_path(readme_path, root)} from {display_path(csv_path, root)} with {len(rows)} row(s)") return 0 From 98b4e9d5cc9b83a7d03df2986a3c771a1c1c2492 Mon Sep 17 00:00:00 2001 From: ik4rd Date: Sun, 31 May 2026 15:55:57 +0300 Subject: [PATCH 07/16] upd: update dashboard --- README.md | 139 ++++++++++++----------- benchmarks/results/readme_benchmarks.csv | 86 +++++++------- 2 files changed, 117 insertions(+), 108 deletions(-) diff --git a/README.md b/README.md index 2dfe8d1..cc7c856 100644 --- a/README.md +++ b/README.md @@ -2,10 +2,19 @@ Колононый движок с нуля (in progress) +## Среда выполнения benchmark'ов + +- Устройство: MacBook Pro (`MacBookPro17,1`) +- Процессор: Apple M1 +- Ядра CPU: 8 (`4` performance + `4` efficiency) +- Оперативная память: 8 GB +- Архитектура: `arm64` +- ОС: macOS `26.3` (`25D5101c`) + ## Benchmark Dashboard -`generated 2026-05-29 18:28:35` · `queries 43/43` · `warm runs 4` · `cache drop per-query` · [`csv`](benchmarks/results/readme_benchmarks.csv) +`generated 2026-05-31 15:54:25` · `queries 43/43` · `warm runs 4` · `cache drop none` · `query mode hardcoded` · [`csv`](benchmarks/results/readme_benchmarks.csv) @@ -16,14 +25,14 @@ - - - - - - - - + + + + + + + +
queries ok43 / 43
queries failed0
median warm time228.91 ms
average warm time250.97 ms
p95 warm time484.49 ms
cold/warm delta69.2%
total output size20.3 KB
max output size6.0 KB
fastest queryQ00 · 4.32 ms
slowest queryQ28 · 1037.26 ms
median warm time152.25 ms
average warm time194.25 ms
p95 warm time518.62 ms
cold/warm delta-8.4%
total output size0 B
max output size0 B
fastest queryQ00 · <1 ms
slowest queryQ28 · 919.50 ms
@@ -40,10 +49,10 @@ roundtrip csv size743.0 MB compression ratio6.71x columnar / csv14.9% -csv -> columnar6.15s -columnar -> csv9.81s -convert throughput130.5 MB/s -roundtrip throughput12.2 MB/s +csv -> columnar5.69s +columnar -> csv9.57s +convert throughput141.0 MB/s +roundtrip throughput12.5 MB/s @@ -52,64 +61,64 @@ ### Heatmap -`🟩` быстрее медианы · `🟦` около медианы · `🟥` медленнее медианы · `⬜` 0 ms +`🟩` быстрее медианы · `🟦` около медианы · `🟥` медленнее медианы · `⬜` <1 ms | slot 1 | slot 2 | slot 3 | slot 4 | slot 5 | slot 6 | | ---: | ---: | ---: | ---: | ---: | ---: | -| Q00 🟩 `4.32` | Q01 🟩 `11.76` | Q02 🟩 `22.01` | Q03 🟩 `18.75` | Q04 🟩 `149.04` | Q05 🟩 `130.82` | -| Q06 🟩 `4.89` | Q07 🟩 `34.49` | Q08 🟦 `209.66` | Q09 🟦 `210.86` | Q10 🟥 `321.56` | Q11 🟥 `269.94` | -| Q12 🟦 `225.75` | Q13 🟥 `254.04` | Q14 🟦 `229.45` | Q15 🟩 `77.77` | Q16 🟩 `123.55` | Q17 🟩 `155.37` | -| Q18 🟥 `409.06` | Q19 🟩 `4.78` | Q20 🟦 `250.25` | Q21 🟥 `274.39` | Q22 🟥 `379.26` | Q23 🟥 `259.48` | -| Q24 🟦 `244.56` | Q25 🟦 `228.91` | Q26 🟦 `225.43` | Q27 🟥 `484.59` | Q28 🟥 `1037.26` | Q29 🟥 `483.60` | -| Q30 🟦 `247.41` | Q31 🟥 `289.23` | Q32 🟥 `970.94` | Q33 🟥 `435.10` | Q34 🟥 `467.90` | Q35 🟩 `185.10` | -| Q36 🟥 `470.23` | Q37 🟥 `272.08` | Q38 🟩 `72.51` | Q39 🟥 `447.60` | Q40 🟩 `93.99` | Q41 🟩 `34.60` | -| Q42 🟩 `69.50` | | | | | | +| Q00 ⬜ `<1` | Q01 🟩 `4.00` | Q02 🟩 `8.50` | Q03 🟩 `6.00` | Q04 🟩 `34.50` | Q05 🟩 `37.50` | +| Q06 ⬜ `<1` | Q07 🟩 `7.00` | Q08 🟥 `191.00` | Q09 🟥 `214.00` | Q10 🟥 `272.00` | Q11 🟥 `267.50` | +| Q12 🟥 `225.50` | Q13 🟥 `259.00` | Q14 🟥 `227.00` | Q15 🟩 `99.00` | Q16 🟦 `149.50` | Q17 🟦 `162.50` | +| Q18 🟥 `510.00` | Q19 ⬜ `<1` | Q20 🟦 `137.50` | Q21 🟦 `155.00` | Q22 🟥 `286.00` | Q23 🟥 `245.00` | +| Q24 🟩 `39.00` | Q25 🟩 `35.00` | Q26 🟩 `38.00` | Q27 🟥 `453.00` | Q28 🟥 `919.50` | Q29 🟩 `7.50` | +| Q30 🟩 `38.00` | Q31 🟩 `56.00` | Q32 🟥 `682.50` | Q33 🟥 `347.50` | Q34 🟥 `333.50` | Q35 🟩 `20.50` | +| Q36 🟥 `369.00` | Q37 🟥 `240.00` | Q38 🟩 `70.50` | Q39 🟥 `461.00` | Q40 🟩 `64.50` | Q41 🟩 `32.50` | +| Q42 🟩 `64.50` | | | | | | ### Query Table | Query | Output CSV | First run, ms | Warm avg, ms | Warm median, ms | Warm min, ms | Warm max, ms | Status | | --- | ---: | ---: | ---: | ---: | ---: | ---: | --- | -| [Q00](benchmarks/queries/query_0.sql) | 7 B | 113.25 | 4.35 | 4.32 | 4.12 | 4.63 | ok | -| [Q01](benchmarks/queries/query_1.sql) | 6 B | 137.27 | 21.96 | 11.76 | 11.52 | 52.82 | ok | -| [Q02](benchmarks/queries/query_2.sql) | 18 B | 142.08 | 22.02 | 22.01 | 21.74 | 22.31 | ok | -| [Q03](benchmarks/queries/query_3.sql) | 20 B | 123.64 | 18.46 | 18.75 | 16.21 | 20.13 | ok | -| [Q04](benchmarks/queries/query_4.sql) | 6 B | 234.96 | 150.05 | 149.04 | 148.08 | 154.04 | ok | -| [Q05](benchmarks/queries/query_5.sql) | 6 B | 240.36 | 130.98 | 130.82 | 129.98 | 132.32 | ok | -| [Q06](benchmarks/queries/query_6.sql) | 22 B | 85.34 | 5.07 | 4.89 | 4.46 | 6.01 | ok | -| [Q07](benchmarks/queries/query_7.sql) | 31 B | 239.83 | 31.86 | 34.49 | 14.17 | 44.29 | ok | -| [Q08](benchmarks/queries/query_8.sql) | 81 B | 580.94 | 209.67 | 209.66 | 195.59 | 223.77 | ok | -| [Q09](benchmarks/queries/query_9.sql) | 237 B | 375.45 | 212.19 | 210.86 | 210.84 | 216.20 | ok | -| [Q10](benchmarks/queries/query_10.sql) | 102 B | 398.40 | 330.03 | 321.56 | 268.15 | 408.83 | ok | -| [Q11](benchmarks/queries/query_11.sql) | 116 B | 406.75 | 270.12 | 269.94 | 268.30 | 272.31 | ok | -| [Q12](benchmarks/queries/query_12.sql) | 387 B | 358.79 | 226.49 | 225.75 | 224.20 | 230.24 | ok | -| [Q13](benchmarks/queries/query_13.sql) | 350 B | 418.79 | 254.61 | 254.04 | 252.88 | 257.49 | ok | -| [Q14](benchmarks/queries/query_14.sql) | 400 B | 432.13 | 229.34 | 229.45 | 226.48 | 231.97 | ok | -| [Q15](benchmarks/queries/query_15.sql) | 236 B | 202.02 | 77.58 | 77.77 | 75.24 | 79.54 | ok | -| [Q16](benchmarks/queries/query_16.sql) | 244 B | 328.95 | 123.58 | 123.55 | 122.32 | 124.89 | ok | -| [Q17](benchmarks/queries/query_17.sql) | 414 B | 314.07 | 156.98 | 155.37 | 154.82 | 162.36 | ok | -| [Q18](benchmarks/queries/query_18.sql) | 264 B | 593.68 | 397.69 | 409.06 | 359.36 | 413.31 | ok | -| [Q19](benchmarks/queries/query_19.sql) | 0 B | 136.87 | 4.81 | 4.78 | 4.61 | 5.08 | ok | -| [Q20](benchmarks/queries/query_20.sql) | 3 B | 429.77 | 250.00 | 250.25 | 248.39 | 251.10 | ok | -| [Q21](benchmarks/queries/query_21.sql) | 162 B | 501.89 | 275.00 | 274.39 | 272.76 | 278.44 | ok | -| [Q22](benchmarks/queries/query_22.sql) | 6.0 KB | 613.76 | 388.76 | 379.26 | 374.02 | 422.49 | ok | -| [Q23](benchmarks/queries/query_23.sql) | 2.6 KB | 535.96 | 256.21 | 259.48 | 244.46 | 261.44 | ok | -| [Q24](benchmarks/queries/query_24.sql) | 672 B | 481.76 | 244.80 | 244.56 | 238.24 | 251.85 | ok | -| [Q25](benchmarks/queries/query_25.sql) | 382 B | 323.60 | 237.19 | 228.91 | 221.17 | 269.75 | ok | -| [Q26](benchmarks/queries/query_26.sql) | 658 B | 524.01 | 225.42 | 225.43 | 223.30 | 227.52 | ok | -| [Q27](benchmarks/queries/query_27.sql) | 26 B | 630.87 | 538.84 | 484.59 | 473.97 | 712.22 | ok | -| [Q28](benchmarks/queries/query_28.sql) | 556 B | 1281.69 | 1032.44 | 1037.26 | 974.01 | 1081.23 | ok | -| [Q29](benchmarks/queries/query_29.sql) | 990 B | 688.07 | 492.24 | 483.60 | 456.93 | 544.81 | ok | -| [Q30](benchmarks/queries/query_30.sql) | 244 B | 417.32 | 247.59 | 247.41 | 242.87 | 252.69 | ok | -| [Q31](benchmarks/queries/query_31.sql) | 398 B | 537.23 | 288.86 | 289.23 | 274.97 | 302.04 | ok | -| [Q32](benchmarks/queries/query_32.sql) | 376 B | 1146.97 | 972.70 | 970.94 | 952.74 | 996.17 | ok | -| [Q33](benchmarks/queries/query_33.sql) | 464 B | 565.29 | 445.48 | 435.10 | 373.51 | 538.19 | ok | -| [Q34](benchmarks/queries/query_34.sql) | 484 B | 601.70 | 470.54 | 467.90 | 440.92 | 505.46 | ok | -| [Q35](benchmarks/queries/query_35.sql) | 493 B | 377.26 | 189.25 | 185.10 | 172.52 | 214.30 | ok | -| [Q36](benchmarks/queries/query_36.sql) | 569 B | 505.12 | 595.08 | 470.23 | 367.62 | 1072.25 | ok | -| [Q37](benchmarks/queries/query_37.sql) | 550 B | 652.50 | 269.23 | 272.08 | 246.11 | 286.66 | ok | -| [Q38](benchmarks/queries/query_38.sql) | 480 B | 221.58 | 75.18 | 72.51 | 71.88 | 83.83 | ok | -| [Q39](benchmarks/queries/query_39.sql) | 835 B | 620.65 | 435.00 | 447.60 | 393.28 | 451.52 | ok | -| [Q40](benchmarks/queries/query_40.sql) | 356 B | 275.34 | 93.89 | 93.99 | 93.48 | 94.10 | ok | -| [Q41](benchmarks/queries/query_41.sql) | 132 B | 246.34 | 34.89 | 34.60 | 33.65 | 36.68 | ok | -| [Q42](benchmarks/queries/query_42.sql) | 240 B | 217.42 | 69.30 | 69.50 | 67.93 | 70.24 | ok | +| [Q00](benchmarks/queries/query_0.sql) | 0 B | <1 | <1 | <1 | <1 | <1 | ok | +| [Q01](benchmarks/queries/query_1.sql) | 0 B | 9.00 | 4.25 | 4.00 | 4.00 | 5.00 | ok | +| [Q02](benchmarks/queries/query_2.sql) | 0 B | 11.00 | 8.50 | 8.50 | 7.00 | 10.00 | ok | +| [Q03](benchmarks/queries/query_3.sql) | 0 B | 12.00 | 6.00 | 6.00 | 6.00 | 6.00 | ok | +| [Q04](benchmarks/queries/query_4.sql) | 0 B | 33.00 | 34.75 | 34.50 | 34.00 | 36.00 | ok | +| [Q05](benchmarks/queries/query_5.sql) | 0 B | 44.00 | 37.50 | 37.50 | 36.00 | 39.00 | ok | +| [Q06](benchmarks/queries/query_6.sql) | 0 B | <1 | <1 | <1 | <1 | <1 | ok | +| [Q07](benchmarks/queries/query_7.sql) | 0 B | 8.00 | 7.00 | 7.00 | 7.00 | 7.00 | ok | +| [Q08](benchmarks/queries/query_8.sql) | 0 B | 189.00 | 190.75 | 191.00 | 188.00 | 193.00 | ok | +| [Q09](benchmarks/queries/query_9.sql) | 0 B | 202.00 | 225.00 | 214.00 | 209.00 | 263.00 | ok | +| [Q10](benchmarks/queries/query_10.sql) | 0 B | 267.00 | 272.50 | 272.00 | 268.00 | 278.00 | ok | +| [Q11](benchmarks/queries/query_11.sql) | 0 B | 264.00 | 268.50 | 267.50 | 266.00 | 273.00 | ok | +| [Q12](benchmarks/queries/query_12.sql) | 0 B | 222.00 | 226.25 | 225.50 | 223.00 | 231.00 | ok | +| [Q13](benchmarks/queries/query_13.sql) | 0 B | 253.00 | 258.25 | 259.00 | 252.00 | 263.00 | ok | +| [Q14](benchmarks/queries/query_14.sql) | 0 B | 222.00 | 226.50 | 227.00 | 222.00 | 230.00 | ok | +| [Q15](benchmarks/queries/query_15.sql) | 0 B | 90.00 | 98.75 | 99.00 | 91.00 | 106.00 | ok | +| [Q16](benchmarks/queries/query_16.sql) | 0 B | 140.00 | 151.50 | 149.50 | 144.00 | 163.00 | ok | +| [Q17](benchmarks/queries/query_17.sql) | 0 B | 151.00 | 162.75 | 162.50 | 158.00 | 168.00 | ok | +| [Q18](benchmarks/queries/query_18.sql) | 0 B | 512.00 | 511.25 | 510.00 | 504.00 | 521.00 | ok | +| [Q19](benchmarks/queries/query_19.sql) | 0 B | <1 | 0.25 | <1 | <1 | 1.00 | ok | +| [Q20](benchmarks/queries/query_20.sql) | 0 B | 156.00 | 137.25 | 137.50 | 136.00 | 138.00 | ok | +| [Q21](benchmarks/queries/query_21.sql) | 0 B | 152.00 | 155.50 | 155.00 | 154.00 | 158.00 | ok | +| [Q22](benchmarks/queries/query_22.sql) | 0 B | 312.00 | 285.50 | 286.00 | 282.00 | 288.00 | ok | +| [Q23](benchmarks/queries/query_23.sql) | 0 B | 258.00 | 245.00 | 245.00 | 238.00 | 252.00 | ok | +| [Q24](benchmarks/queries/query_24.sql) | 0 B | 36.00 | 38.25 | 39.00 | 36.00 | 39.00 | ok | +| [Q25](benchmarks/queries/query_25.sql) | 0 B | 35.00 | 36.25 | 35.00 | 35.00 | 40.00 | ok | +| [Q26](benchmarks/queries/query_26.sql) | 0 B | 37.00 | 41.25 | 38.00 | 37.00 | 52.00 | ok | +| [Q27](benchmarks/queries/query_27.sql) | 0 B | 441.00 | 452.00 | 453.00 | 441.00 | 461.00 | ok | +| [Q28](benchmarks/queries/query_28.sql) | 0 B | 961.00 | 919.25 | 919.50 | 915.00 | 923.00 | ok | +| [Q29](benchmarks/queries/query_29.sql) | 0 B | 5.00 | 7.00 | 7.50 | 4.00 | 9.00 | ok | +| [Q30](benchmarks/queries/query_30.sql) | 0 B | 40.00 | 37.75 | 38.00 | 36.00 | 39.00 | ok | +| [Q31](benchmarks/queries/query_31.sql) | 0 B | 50.00 | 55.00 | 56.00 | 50.00 | 58.00 | ok | +| [Q32](benchmarks/queries/query_32.sql) | 0 B | 626.00 | 820.25 | 682.50 | 668.00 | 1248.00 | ok | +| [Q33](benchmarks/queries/query_33.sql) | 0 B | 314.00 | 351.75 | 347.50 | 340.00 | 372.00 | ok | +| [Q34](benchmarks/queries/query_34.sql) | 0 B | 302.00 | 333.25 | 333.50 | 328.00 | 338.00 | ok | +| [Q35](benchmarks/queries/query_35.sql) | 0 B | 20.00 | 21.25 | 20.50 | 19.00 | 25.00 | ok | +| [Q36](benchmarks/queries/query_36.sql) | 0 B | 351.00 | 379.75 | 369.00 | 363.00 | 418.00 | ok | +| [Q37](benchmarks/queries/query_37.sql) | 0 B | 233.00 | 245.00 | 240.00 | 240.00 | 260.00 | ok | +| [Q38](benchmarks/queries/query_38.sql) | 0 B | 68.00 | 71.75 | 70.50 | 69.00 | 77.00 | ok | +| [Q39](benchmarks/queries/query_39.sql) | 0 B | 460.00 | 475.25 | 461.00 | 459.00 | 520.00 | ok | +| [Q40](benchmarks/queries/query_40.sql) | 0 B | 72.00 | 64.25 | 64.50 | 60.00 | 68.00 | ok | +| [Q41](benchmarks/queries/query_41.sql) | 0 B | 31.00 | 32.50 | 32.50 | 30.00 | 35.00 | ok | +| [Q42](benchmarks/queries/query_42.sql) | 0 B | 64.00 | 65.00 | 64.50 | 64.00 | 67.00 | ok | diff --git a/benchmarks/results/readme_benchmarks.csv b/benchmarks/results/readme_benchmarks.csv index 8e9cec8..8cb6fe6 100644 --- a/benchmarks/results/readme_benchmarks.csv +++ b/benchmarks/results/readme_benchmarks.csv @@ -1,44 +1,44 @@ query_id,query_file,sql,first_run_ms,warm_avg_ms,warm_median_ms,warm_min_ms,warm_max_ms,output_csv_bytes,status,error -0,benchmarks/queries/query_0.sql,SELECT COUNT(*) FROM hits;,113.25,4.35,4.32,4.12,4.63,7,ok, -1,benchmarks/queries/query_1.sql,SELECT COUNT(*) FROM hits WHERE AdvEngineID <> 0;,137.27,21.96,11.76,11.52,52.82,6,ok, -2,benchmarks/queries/query_2.sql,"SELECT SUM(AdvEngineID), COUNT(*), AVG(ResolutionWidth) FROM hits;",142.08,22.02,22.01,21.74,22.31,18,ok, -3,benchmarks/queries/query_3.sql,SELECT AVG(UserID) FROM hits;,123.64,18.46,18.75,16.21,20.13,20,ok, -4,benchmarks/queries/query_4.sql,SELECT COUNT(DISTINCT UserID) FROM hits;,234.96,150.05,149.04,148.08,154.04,6,ok, -5,benchmarks/queries/query_5.sql,SELECT COUNT(DISTINCT SearchPhrase) FROM hits;,240.36,130.98,130.82,129.98,132.32,6,ok, -6,benchmarks/queries/query_6.sql,"SELECT MIN(EventDate), MAX(EventDate) FROM hits;",85.34,5.07,4.89,4.46,6.01,22,ok, -7,benchmarks/queries/query_7.sql,"SELECT AdvEngineID, COUNT(*) FROM hits WHERE AdvEngineID <> 0 GROUP BY AdvEngineID ORDER BY COUNT(*) DESC;",239.83,31.86,34.49,14.17,44.29,31,ok, -8,benchmarks/queries/query_8.sql,"SELECT RegionID, COUNT(DISTINCT UserID) AS u FROM hits GROUP BY RegionID ORDER BY u DESC LIMIT 10;",580.94,209.67,209.66,195.59,223.77,81,ok, -9,benchmarks/queries/query_9.sql,"SELECT RegionID, SUM(AdvEngineID), COUNT(*) AS c, AVG(ResolutionWidth), COUNT(DISTINCT UserID) FROM hits GROUP BY RegionID ORDER BY c DESC LIMIT 10;",375.45,212.19,210.86,210.84,216.20,237,ok, -10,benchmarks/queries/query_10.sql,"SELECT MobilePhoneModel, COUNT(DISTINCT UserID) AS u FROM hits WHERE MobilePhoneModel <> '' GROUP BY MobilePhoneModel ORDER BY u DESC LIMIT 10;",398.40,330.03,321.56,268.15,408.83,102,ok, -11,benchmarks/queries/query_11.sql,"SELECT MobilePhone, MobilePhoneModel, COUNT(DISTINCT UserID) AS u FROM hits WHERE MobilePhoneModel <> '' GROUP BY MobilePhone, MobilePhoneModel ORDER BY u DESC LIMIT 10;",406.75,270.12,269.94,268.30,272.31,116,ok, -12,benchmarks/queries/query_12.sql,"SELECT SearchPhrase, COUNT(*) AS c FROM hits WHERE SearchPhrase <> '' GROUP BY SearchPhrase ORDER BY c DESC LIMIT 10;",358.79,226.49,225.75,224.20,230.24,387,ok, -13,benchmarks/queries/query_13.sql,"SELECT SearchPhrase, COUNT(DISTINCT UserID) AS u FROM hits WHERE SearchPhrase <> '' GROUP BY SearchPhrase ORDER BY u DESC LIMIT 10;",418.79,254.61,254.04,252.88,257.49,350,ok, -14,benchmarks/queries/query_14.sql,"SELECT SearchEngineID, SearchPhrase, COUNT(*) AS c FROM hits WHERE SearchPhrase <> '' GROUP BY SearchEngineID, SearchPhrase ORDER BY c DESC LIMIT 10;",432.13,229.34,229.45,226.48,231.97,400,ok, -15,benchmarks/queries/query_15.sql,"SELECT UserID, COUNT(*) FROM hits GROUP BY UserID ORDER BY COUNT(*) DESC LIMIT 10;",202.02,77.58,77.77,75.24,79.54,236,ok, -16,benchmarks/queries/query_16.sql,"SELECT UserID, SearchPhrase, COUNT(*) FROM hits GROUP BY UserID, SearchPhrase ORDER BY COUNT(*) DESC LIMIT 10;",328.95,123.58,123.55,122.32,124.89,244,ok, -17,benchmarks/queries/query_17.sql,"SELECT UserID, SearchPhrase, COUNT(*) FROM hits GROUP BY UserID, SearchPhrase LIMIT 10;",314.07,156.98,155.37,154.82,162.36,414,ok, -18,benchmarks/queries/query_18.sql,"SELECT UserID, extract(minute FROM EventTime) AS m, SearchPhrase, COUNT(*) FROM hits GROUP BY UserID, m, SearchPhrase ORDER BY COUNT(*) DESC LIMIT 10;",593.68,397.69,409.06,359.36,413.31,264,ok, -19,benchmarks/queries/query_19.sql,SELECT UserID FROM hits WHERE UserID = 435090932899640449;,136.87,4.81,4.78,4.61,5.08,0,ok, -20,benchmarks/queries/query_20.sql,SELECT COUNT(*) FROM hits WHERE URL LIKE '%google%';,429.77,250.00,250.25,248.39,251.10,3,ok, -21,benchmarks/queries/query_21.sql,"SELECT SearchPhrase, MIN(URL), COUNT(*) AS c FROM hits WHERE URL LIKE '%google%' AND SearchPhrase <> '' GROUP BY SearchPhrase ORDER BY c DESC LIMIT 10;",501.89,275.00,274.39,272.76,278.44,162,ok, -22,benchmarks/queries/query_22.sql,"SELECT SearchPhrase, MIN(URL), MIN(Title), COUNT(*) AS c, COUNT(DISTINCT UserID) FROM hits WHERE Title LIKE '%Google%' AND URL NOT LIKE '%.google.%' AND SearchPhrase <> '' GROUP BY SearchPhrase ORDER BY c DESC LIMIT 10;",613.76,388.76,379.26,374.02,422.49,6188,ok, -23,benchmarks/queries/query_23.sql,SELECT * FROM hits WHERE URL LIKE '%google%' ORDER BY EventTime LIMIT 10;,535.96,256.21,259.48,244.46,261.44,2633,ok, -24,benchmarks/queries/query_24.sql,SELECT SearchPhrase FROM hits WHERE SearchPhrase <> '' ORDER BY EventTime LIMIT 10;,481.76,244.80,244.56,238.24,251.85,672,ok, -25,benchmarks/queries/query_25.sql,SELECT SearchPhrase FROM hits WHERE SearchPhrase <> '' ORDER BY SearchPhrase LIMIT 10;,323.60,237.19,228.91,221.17,269.75,382,ok, -26,benchmarks/queries/query_26.sql,"SELECT SearchPhrase FROM hits WHERE SearchPhrase <> '' ORDER BY EventTime, SearchPhrase LIMIT 10;",524.01,225.42,225.43,223.30,227.52,658,ok, -27,benchmarks/queries/query_27.sql,"SELECT CounterID, AVG(STRLEN(URL)) AS l, COUNT(*) AS c FROM hits WHERE URL <> '' GROUP BY CounterID HAVING COUNT(*) > 100000 ORDER BY l DESC LIMIT 25;",630.87,538.84,484.59,473.97,712.22,26,ok, -28,benchmarks/queries/query_28.sql,"SELECT REGEXP_REPLACE(Referer, '^https?://(?:www\.)?([^/]+)/.*$', '\1') AS k, AVG(STRLEN(Referer)) AS l, COUNT(*) AS c, MIN(Referer) FROM hits WHERE Referer <> '' GROUP BY k HAVING COUNT(*) > 100000 ORDER BY l DESC LIMIT 25;",1281.69,1032.44,1037.26,974.01,1081.23,556,ok, -29,benchmarks/queries/query_29.sql,"SELECT SUM(ResolutionWidth), SUM(ResolutionWidth + 1), SUM(ResolutionWidth + 2), SUM(ResolutionWidth + 3), SUM(ResolutionWidth + 4), SUM(ResolutionWidth + 5), SUM(ResolutionWidth + 6), SUM(ResolutionWidth + 7), SUM(ResolutionWidth + 8), SUM(ResolutionWidth + 9), SUM(ResolutionWidth + 10), SUM(ResolutionWidth + 11), SUM(ResolutionWidth + 12), SUM(ResolutionWidth + 13), SUM(ResolutionWidth + 14), SUM(ResolutionWidth + 15), SUM(ResolutionWidth + 16), SUM(ResolutionWidth + 17), SUM(ResolutionWidth + 18), SUM(ResolutionWidth + 19), SUM(ResolutionWidth + 20), SUM(ResolutionWidth + 21), SUM(ResolutionWidth + 22), SUM(ResolutionWidth + 23), SUM(ResolutionWidth + 24), SUM(ResolutionWidth + 25), SUM(ResolutionWidth + 26), SUM(ResolutionWidth + 27), SUM(ResolutionWidth + 28), SUM(ResolutionWidth + 29), SUM(ResolutionWidth + 30), SUM(ResolutionWidth + 31), SUM(ResolutionWidth + 32), SUM(ResolutionWidth + 33), SUM(ResolutionWidth + 34), SUM(ResolutionWidth + 35), SUM(ResolutionWidth + 36), SUM(ResolutionWidth + 37), SUM(ResolutionWidth + 38), SUM(ResolutionWidth + 39), SUM(ResolutionWidth + 40), SUM(ResolutionWidth + 41), SUM(ResolutionWidth + 42), SUM(ResolutionWidth + 43), SUM(ResolutionWidth + 44), SUM(ResolutionWidth + 45), SUM(ResolutionWidth + 46), SUM(ResolutionWidth + 47), SUM(ResolutionWidth + 48), SUM(ResolutionWidth + 49), SUM(ResolutionWidth + 50), SUM(ResolutionWidth + 51), SUM(ResolutionWidth + 52), SUM(ResolutionWidth + 53), SUM(ResolutionWidth + 54), SUM(ResolutionWidth + 55), SUM(ResolutionWidth + 56), SUM(ResolutionWidth + 57), SUM(ResolutionWidth + 58), SUM(ResolutionWidth + 59), SUM(ResolutionWidth + 60), SUM(ResolutionWidth + 61), SUM(ResolutionWidth + 62), SUM(ResolutionWidth + 63), SUM(ResolutionWidth + 64), SUM(ResolutionWidth + 65), SUM(ResolutionWidth + 66), SUM(ResolutionWidth + 67), SUM(ResolutionWidth + 68), SUM(ResolutionWidth + 69), SUM(ResolutionWidth + 70), SUM(ResolutionWidth + 71), SUM(ResolutionWidth + 72), SUM(ResolutionWidth + 73), SUM(ResolutionWidth + 74), SUM(ResolutionWidth + 75), SUM(ResolutionWidth + 76), SUM(ResolutionWidth + 77), SUM(ResolutionWidth + 78), SUM(ResolutionWidth + 79), SUM(ResolutionWidth + 80), SUM(ResolutionWidth + 81), SUM(ResolutionWidth + 82), SUM(ResolutionWidth + 83), SUM(ResolutionWidth + 84), SUM(ResolutionWidth + 85), SUM(ResolutionWidth + 86), SUM(ResolutionWidth + 87), SUM(ResolutionWidth + 88), SUM(ResolutionWidth + 89) FROM hits;",688.07,492.24,483.60,456.93,544.81,990,ok, -30,benchmarks/queries/query_30.sql,"SELECT SearchEngineID, ClientIP, COUNT(*) AS c, SUM(IsRefresh), AVG(ResolutionWidth) FROM hits WHERE SearchPhrase <> '' GROUP BY SearchEngineID, ClientIP ORDER BY c DESC LIMIT 10;",417.32,247.59,247.41,242.87,252.69,244,ok, -31,benchmarks/queries/query_31.sql,"SELECT WatchID, ClientIP, COUNT(*) AS c, SUM(IsRefresh), AVG(ResolutionWidth) FROM hits WHERE SearchPhrase <> '' GROUP BY WatchID, ClientIP ORDER BY c DESC LIMIT 10;",537.23,288.86,289.23,274.97,302.04,398,ok, -32,benchmarks/queries/query_32.sql,"SELECT WatchID, ClientIP, COUNT(*) AS c, SUM(IsRefresh), AVG(ResolutionWidth) FROM hits GROUP BY WatchID, ClientIP ORDER BY c DESC LIMIT 10;",1146.97,972.70,970.94,952.74,996.17,376,ok, -33,benchmarks/queries/query_33.sql,"SELECT URL, COUNT(*) AS c FROM hits GROUP BY URL ORDER BY c DESC LIMIT 10;",565.29,445.48,435.10,373.51,538.19,464,ok, -34,benchmarks/queries/query_34.sql,"SELECT 1, URL, COUNT(*) AS c FROM hits GROUP BY 1, URL ORDER BY c DESC LIMIT 10;",601.70,470.54,467.90,440.92,505.46,484,ok, -35,benchmarks/queries/query_35.sql,"SELECT ClientIP, ClientIP - 1, ClientIP - 2, ClientIP - 3, COUNT(*) AS c FROM hits GROUP BY ClientIP, ClientIP - 1, ClientIP - 2, ClientIP - 3 ORDER BY c DESC LIMIT 10;",377.26,189.25,185.10,172.52,214.30,493,ok, -36,benchmarks/queries/query_36.sql,"SELECT URL, COUNT(*) AS PageViews FROM hits WHERE CounterID = 62 AND EventDate >= '2013-07-01' AND EventDate <= '2013-07-31' AND DontCountHits = 0 AND IsRefresh = 0 AND URL <> '' GROUP BY URL ORDER BY PageViews DESC LIMIT 10;",505.12,595.08,470.23,367.62,1072.25,569,ok, -37,benchmarks/queries/query_37.sql,"SELECT Title, COUNT(*) AS PageViews FROM hits WHERE CounterID = 62 AND EventDate >= '2013-07-01' AND EventDate <= '2013-07-31' AND DontCountHits = 0 AND IsRefresh = 0 AND Title <> '' GROUP BY Title ORDER BY PageViews DESC LIMIT 10;",652.50,269.23,272.08,246.11,286.66,550,ok, -38,benchmarks/queries/query_38.sql,"SELECT URL, COUNT(*) AS PageViews FROM hits WHERE CounterID = 62 AND EventDate >= '2013-07-01' AND EventDate <= '2013-07-31' AND IsRefresh = 0 AND IsLink <> 0 AND IsDownload = 0 GROUP BY URL ORDER BY PageViews DESC LIMIT 10 OFFSET 1000;",221.58,75.18,72.51,71.88,83.83,480,ok, -39,benchmarks/queries/query_39.sql,"SELECT TraficSourceID, SearchEngineID, AdvEngineID, CASE WHEN (SearchEngineID = 0 AND AdvEngineID = 0) THEN Referer ELSE '' END AS Src, URL AS Dst, COUNT(*) AS PageViews FROM hits WHERE CounterID = 62 AND EventDate >= '2013-07-01' AND EventDate <= '2013-07-31' AND IsRefresh = 0 GROUP BY TraficSourceID, SearchEngineID, AdvEngineID, Src, Dst ORDER BY PageViews DESC LIMIT 10 OFFSET 1000;",620.65,435.00,447.60,393.28,451.52,835,ok, -40,benchmarks/queries/query_40.sql,"SELECT URLHash, EventDate, COUNT(*) AS PageViews FROM hits WHERE CounterID = 62 AND EventDate >= '2013-07-01' AND EventDate <= '2013-07-31' AND IsRefresh = 0 AND TraficSourceID IN (-1, 6) AND RefererHash = 3594120000172545465 GROUP BY URLHash, EventDate ORDER BY PageViews DESC LIMIT 10 OFFSET 100;",275.34,93.89,93.99,93.48,94.10,356,ok, -41,benchmarks/queries/query_41.sql,"SELECT WindowClientWidth, WindowClientHeight, COUNT(*) AS PageViews FROM hits WHERE CounterID = 62 AND EventDate >= '2013-07-01' AND EventDate <= '2013-07-31' AND IsRefresh = 0 AND DontCountHits = 0 AND URLHash = 2868770270353813622 GROUP BY WindowClientWidth, WindowClientHeight ORDER BY PageViews DESC LIMIT 10 OFFSET 10000;",246.34,34.89,34.60,33.65,36.68,132,ok, -42,benchmarks/queries/query_42.sql,"SELECT DATE_TRUNC('minute', EventTime) AS M, COUNT(*) AS PageViews FROM hits WHERE CounterID = 62 AND EventDate >= '2013-07-14' AND EventDate <= '2013-07-15' AND IsRefresh = 0 AND DontCountHits = 0 GROUP BY DATE_TRUNC('minute', EventTime) ORDER BY DATE_TRUNC('minute', EventTime) LIMIT 10 OFFSET 1000;",217.42,69.30,69.50,67.93,70.24,240,ok, +0,benchmarks/queries/query_0.sql,SELECT COUNT(*) FROM hits;,0.00,0.00,0.00,0.00,0.00,0,ok, +1,benchmarks/queries/query_1.sql,SELECT COUNT(*) FROM hits WHERE AdvEngineID <> 0;,9.00,4.25,4.00,4.00,5.00,0,ok, +2,benchmarks/queries/query_2.sql,"SELECT SUM(AdvEngineID), COUNT(*), AVG(ResolutionWidth) FROM hits;",11.00,8.50,8.50,7.00,10.00,0,ok, +3,benchmarks/queries/query_3.sql,SELECT AVG(UserID) FROM hits;,12.00,6.00,6.00,6.00,6.00,0,ok, +4,benchmarks/queries/query_4.sql,SELECT COUNT(DISTINCT UserID) FROM hits;,33.00,34.75,34.50,34.00,36.00,0,ok, +5,benchmarks/queries/query_5.sql,SELECT COUNT(DISTINCT SearchPhrase) FROM hits;,44.00,37.50,37.50,36.00,39.00,0,ok, +6,benchmarks/queries/query_6.sql,"SELECT MIN(EventDate), MAX(EventDate) FROM hits;",0.00,0.00,0.00,0.00,0.00,0,ok, +7,benchmarks/queries/query_7.sql,"SELECT AdvEngineID, COUNT(*) FROM hits WHERE AdvEngineID <> 0 GROUP BY AdvEngineID ORDER BY COUNT(*) DESC;",8.00,7.00,7.00,7.00,7.00,0,ok, +8,benchmarks/queries/query_8.sql,"SELECT RegionID, COUNT(DISTINCT UserID) AS u FROM hits GROUP BY RegionID ORDER BY u DESC LIMIT 10;",189.00,190.75,191.00,188.00,193.00,0,ok, +9,benchmarks/queries/query_9.sql,"SELECT RegionID, SUM(AdvEngineID), COUNT(*) AS c, AVG(ResolutionWidth), COUNT(DISTINCT UserID) FROM hits GROUP BY RegionID ORDER BY c DESC LIMIT 10;",202.00,225.00,214.00,209.00,263.00,0,ok, +10,benchmarks/queries/query_10.sql,"SELECT MobilePhoneModel, COUNT(DISTINCT UserID) AS u FROM hits WHERE MobilePhoneModel <> '' GROUP BY MobilePhoneModel ORDER BY u DESC LIMIT 10;",267.00,272.50,272.00,268.00,278.00,0,ok, +11,benchmarks/queries/query_11.sql,"SELECT MobilePhone, MobilePhoneModel, COUNT(DISTINCT UserID) AS u FROM hits WHERE MobilePhoneModel <> '' GROUP BY MobilePhone, MobilePhoneModel ORDER BY u DESC LIMIT 10;",264.00,268.50,267.50,266.00,273.00,0,ok, +12,benchmarks/queries/query_12.sql,"SELECT SearchPhrase, COUNT(*) AS c FROM hits WHERE SearchPhrase <> '' GROUP BY SearchPhrase ORDER BY c DESC LIMIT 10;",222.00,226.25,225.50,223.00,231.00,0,ok, +13,benchmarks/queries/query_13.sql,"SELECT SearchPhrase, COUNT(DISTINCT UserID) AS u FROM hits WHERE SearchPhrase <> '' GROUP BY SearchPhrase ORDER BY u DESC LIMIT 10;",253.00,258.25,259.00,252.00,263.00,0,ok, +14,benchmarks/queries/query_14.sql,"SELECT SearchEngineID, SearchPhrase, COUNT(*) AS c FROM hits WHERE SearchPhrase <> '' GROUP BY SearchEngineID, SearchPhrase ORDER BY c DESC LIMIT 10;",222.00,226.50,227.00,222.00,230.00,0,ok, +15,benchmarks/queries/query_15.sql,"SELECT UserID, COUNT(*) FROM hits GROUP BY UserID ORDER BY COUNT(*) DESC LIMIT 10;",90.00,98.75,99.00,91.00,106.00,0,ok, +16,benchmarks/queries/query_16.sql,"SELECT UserID, SearchPhrase, COUNT(*) FROM hits GROUP BY UserID, SearchPhrase ORDER BY COUNT(*) DESC LIMIT 10;",140.00,151.50,149.50,144.00,163.00,0,ok, +17,benchmarks/queries/query_17.sql,"SELECT UserID, SearchPhrase, COUNT(*) FROM hits GROUP BY UserID, SearchPhrase LIMIT 10;",151.00,162.75,162.50,158.00,168.00,0,ok, +18,benchmarks/queries/query_18.sql,"SELECT UserID, extract(minute FROM EventTime) AS m, SearchPhrase, COUNT(*) FROM hits GROUP BY UserID, m, SearchPhrase ORDER BY COUNT(*) DESC LIMIT 10;",512.00,511.25,510.00,504.00,521.00,0,ok, +19,benchmarks/queries/query_19.sql,SELECT UserID FROM hits WHERE UserID = 435090932899640449;,0.00,0.25,0.00,0.00,1.00,0,ok, +20,benchmarks/queries/query_20.sql,SELECT COUNT(*) FROM hits WHERE URL LIKE '%google%';,156.00,137.25,137.50,136.00,138.00,0,ok, +21,benchmarks/queries/query_21.sql,"SELECT SearchPhrase, MIN(URL), COUNT(*) AS c FROM hits WHERE URL LIKE '%google%' AND SearchPhrase <> '' GROUP BY SearchPhrase ORDER BY c DESC LIMIT 10;",152.00,155.50,155.00,154.00,158.00,0,ok, +22,benchmarks/queries/query_22.sql,"SELECT SearchPhrase, MIN(URL), MIN(Title), COUNT(*) AS c, COUNT(DISTINCT UserID) FROM hits WHERE Title LIKE '%Google%' AND URL NOT LIKE '%.google.%' AND SearchPhrase <> '' GROUP BY SearchPhrase ORDER BY c DESC LIMIT 10;",312.00,285.50,286.00,282.00,288.00,0,ok, +23,benchmarks/queries/query_23.sql,SELECT * FROM hits WHERE URL LIKE '%google%' ORDER BY EventTime LIMIT 10;,258.00,245.00,245.00,238.00,252.00,0,ok, +24,benchmarks/queries/query_24.sql,SELECT SearchPhrase FROM hits WHERE SearchPhrase <> '' ORDER BY EventTime LIMIT 10;,36.00,38.25,39.00,36.00,39.00,0,ok, +25,benchmarks/queries/query_25.sql,SELECT SearchPhrase FROM hits WHERE SearchPhrase <> '' ORDER BY SearchPhrase LIMIT 10;,35.00,36.25,35.00,35.00,40.00,0,ok, +26,benchmarks/queries/query_26.sql,"SELECT SearchPhrase FROM hits WHERE SearchPhrase <> '' ORDER BY EventTime, SearchPhrase LIMIT 10;",37.00,41.25,38.00,37.00,52.00,0,ok, +27,benchmarks/queries/query_27.sql,"SELECT CounterID, AVG(STRLEN(URL)) AS l, COUNT(*) AS c FROM hits WHERE URL <> '' GROUP BY CounterID HAVING COUNT(*) > 100000 ORDER BY l DESC LIMIT 25;",441.00,452.00,453.00,441.00,461.00,0,ok, +28,benchmarks/queries/query_28.sql,"SELECT REGEXP_REPLACE(Referer, '^https?://(?:www\.)?([^/]+)/.*$', '\1') AS k, AVG(STRLEN(Referer)) AS l, COUNT(*) AS c, MIN(Referer) FROM hits WHERE Referer <> '' GROUP BY k HAVING COUNT(*) > 100000 ORDER BY l DESC LIMIT 25;",961.00,919.25,919.50,915.00,923.00,0,ok, +29,benchmarks/queries/query_29.sql,"SELECT SUM(ResolutionWidth), SUM(ResolutionWidth + 1), SUM(ResolutionWidth + 2), SUM(ResolutionWidth + 3), SUM(ResolutionWidth + 4), SUM(ResolutionWidth + 5), SUM(ResolutionWidth + 6), SUM(ResolutionWidth + 7), SUM(ResolutionWidth + 8), SUM(ResolutionWidth + 9), SUM(ResolutionWidth + 10), SUM(ResolutionWidth + 11), SUM(ResolutionWidth + 12), SUM(ResolutionWidth + 13), SUM(ResolutionWidth + 14), SUM(ResolutionWidth + 15), SUM(ResolutionWidth + 16), SUM(ResolutionWidth + 17), SUM(ResolutionWidth + 18), SUM(ResolutionWidth + 19), SUM(ResolutionWidth + 20), SUM(ResolutionWidth + 21), SUM(ResolutionWidth + 22), SUM(ResolutionWidth + 23), SUM(ResolutionWidth + 24), SUM(ResolutionWidth + 25), SUM(ResolutionWidth + 26), SUM(ResolutionWidth + 27), SUM(ResolutionWidth + 28), SUM(ResolutionWidth + 29), SUM(ResolutionWidth + 30), SUM(ResolutionWidth + 31), SUM(ResolutionWidth + 32), SUM(ResolutionWidth + 33), SUM(ResolutionWidth + 34), SUM(ResolutionWidth + 35), SUM(ResolutionWidth + 36), SUM(ResolutionWidth + 37), SUM(ResolutionWidth + 38), SUM(ResolutionWidth + 39), SUM(ResolutionWidth + 40), SUM(ResolutionWidth + 41), SUM(ResolutionWidth + 42), SUM(ResolutionWidth + 43), SUM(ResolutionWidth + 44), SUM(ResolutionWidth + 45), SUM(ResolutionWidth + 46), SUM(ResolutionWidth + 47), SUM(ResolutionWidth + 48), SUM(ResolutionWidth + 49), SUM(ResolutionWidth + 50), SUM(ResolutionWidth + 51), SUM(ResolutionWidth + 52), SUM(ResolutionWidth + 53), SUM(ResolutionWidth + 54), SUM(ResolutionWidth + 55), SUM(ResolutionWidth + 56), SUM(ResolutionWidth + 57), SUM(ResolutionWidth + 58), SUM(ResolutionWidth + 59), SUM(ResolutionWidth + 60), SUM(ResolutionWidth + 61), SUM(ResolutionWidth + 62), SUM(ResolutionWidth + 63), SUM(ResolutionWidth + 64), SUM(ResolutionWidth + 65), SUM(ResolutionWidth + 66), SUM(ResolutionWidth + 67), SUM(ResolutionWidth + 68), SUM(ResolutionWidth + 69), SUM(ResolutionWidth + 70), SUM(ResolutionWidth + 71), SUM(ResolutionWidth + 72), SUM(ResolutionWidth + 73), SUM(ResolutionWidth + 74), SUM(ResolutionWidth + 75), SUM(ResolutionWidth + 76), SUM(ResolutionWidth + 77), SUM(ResolutionWidth + 78), SUM(ResolutionWidth + 79), SUM(ResolutionWidth + 80), SUM(ResolutionWidth + 81), SUM(ResolutionWidth + 82), SUM(ResolutionWidth + 83), SUM(ResolutionWidth + 84), SUM(ResolutionWidth + 85), SUM(ResolutionWidth + 86), SUM(ResolutionWidth + 87), SUM(ResolutionWidth + 88), SUM(ResolutionWidth + 89) FROM hits;",5.00,7.00,7.50,4.00,9.00,0,ok, +30,benchmarks/queries/query_30.sql,"SELECT SearchEngineID, ClientIP, COUNT(*) AS c, SUM(IsRefresh), AVG(ResolutionWidth) FROM hits WHERE SearchPhrase <> '' GROUP BY SearchEngineID, ClientIP ORDER BY c DESC LIMIT 10;",40.00,37.75,38.00,36.00,39.00,0,ok, +31,benchmarks/queries/query_31.sql,"SELECT WatchID, ClientIP, COUNT(*) AS c, SUM(IsRefresh), AVG(ResolutionWidth) FROM hits WHERE SearchPhrase <> '' GROUP BY WatchID, ClientIP ORDER BY c DESC LIMIT 10;",50.00,55.00,56.00,50.00,58.00,0,ok, +32,benchmarks/queries/query_32.sql,"SELECT WatchID, ClientIP, COUNT(*) AS c, SUM(IsRefresh), AVG(ResolutionWidth) FROM hits GROUP BY WatchID, ClientIP ORDER BY c DESC LIMIT 10;",626.00,820.25,682.50,668.00,1248.00,0,ok, +33,benchmarks/queries/query_33.sql,"SELECT URL, COUNT(*) AS c FROM hits GROUP BY URL ORDER BY c DESC LIMIT 10;",314.00,351.75,347.50,340.00,372.00,0,ok, +34,benchmarks/queries/query_34.sql,"SELECT 1, URL, COUNT(*) AS c FROM hits GROUP BY 1, URL ORDER BY c DESC LIMIT 10;",302.00,333.25,333.50,328.00,338.00,0,ok, +35,benchmarks/queries/query_35.sql,"SELECT ClientIP, ClientIP - 1, ClientIP - 2, ClientIP - 3, COUNT(*) AS c FROM hits GROUP BY ClientIP, ClientIP - 1, ClientIP - 2, ClientIP - 3 ORDER BY c DESC LIMIT 10;",20.00,21.25,20.50,19.00,25.00,0,ok, +36,benchmarks/queries/query_36.sql,"SELECT URL, COUNT(*) AS PageViews FROM hits WHERE CounterID = 62 AND EventDate >= '2013-07-01' AND EventDate <= '2013-07-31' AND DontCountHits = 0 AND IsRefresh = 0 AND URL <> '' GROUP BY URL ORDER BY PageViews DESC LIMIT 10;",351.00,379.75,369.00,363.00,418.00,0,ok, +37,benchmarks/queries/query_37.sql,"SELECT Title, COUNT(*) AS PageViews FROM hits WHERE CounterID = 62 AND EventDate >= '2013-07-01' AND EventDate <= '2013-07-31' AND DontCountHits = 0 AND IsRefresh = 0 AND Title <> '' GROUP BY Title ORDER BY PageViews DESC LIMIT 10;",233.00,245.00,240.00,240.00,260.00,0,ok, +38,benchmarks/queries/query_38.sql,"SELECT URL, COUNT(*) AS PageViews FROM hits WHERE CounterID = 62 AND EventDate >= '2013-07-01' AND EventDate <= '2013-07-31' AND IsRefresh = 0 AND IsLink <> 0 AND IsDownload = 0 GROUP BY URL ORDER BY PageViews DESC LIMIT 10 OFFSET 1000;",68.00,71.75,70.50,69.00,77.00,0,ok, +39,benchmarks/queries/query_39.sql,"SELECT TraficSourceID, SearchEngineID, AdvEngineID, CASE WHEN (SearchEngineID = 0 AND AdvEngineID = 0) THEN Referer ELSE '' END AS Src, URL AS Dst, COUNT(*) AS PageViews FROM hits WHERE CounterID = 62 AND EventDate >= '2013-07-01' AND EventDate <= '2013-07-31' AND IsRefresh = 0 GROUP BY TraficSourceID, SearchEngineID, AdvEngineID, Src, Dst ORDER BY PageViews DESC LIMIT 10 OFFSET 1000;",460.00,475.25,461.00,459.00,520.00,0,ok, +40,benchmarks/queries/query_40.sql,"SELECT URLHash, EventDate, COUNT(*) AS PageViews FROM hits WHERE CounterID = 62 AND EventDate >= '2013-07-01' AND EventDate <= '2013-07-31' AND IsRefresh = 0 AND TraficSourceID IN (-1, 6) AND RefererHash = 3594120000172545465 GROUP BY URLHash, EventDate ORDER BY PageViews DESC LIMIT 10 OFFSET 100;",72.00,64.25,64.50,60.00,68.00,0,ok, +41,benchmarks/queries/query_41.sql,"SELECT WindowClientWidth, WindowClientHeight, COUNT(*) AS PageViews FROM hits WHERE CounterID = 62 AND EventDate >= '2013-07-01' AND EventDate <= '2013-07-31' AND IsRefresh = 0 AND DontCountHits = 0 AND URLHash = 2868770270353813622 GROUP BY WindowClientWidth, WindowClientHeight ORDER BY PageViews DESC LIMIT 10 OFFSET 10000;",31.00,32.50,32.50,30.00,35.00,0,ok, +42,benchmarks/queries/query_42.sql,"SELECT DATE_TRUNC('minute', EventTime) AS M, COUNT(*) AS PageViews FROM hits WHERE CounterID = 62 AND EventDate >= '2013-07-14' AND EventDate <= '2013-07-15' AND IsRefresh = 0 AND DontCountHits = 0 GROUP BY DATE_TRUNC('minute', EventTime) ORDER BY DATE_TRUNC('minute', EventTime) LIMIT 10 OFFSET 1000;",64.00,65.00,64.50,64.00,67.00,0,ok, From 682266b6a7bf22b33e603e613725e25bedfff6cf Mon Sep 17 00:00:00 2001 From: ik4rd Date: Sun, 31 May 2026 16:45:18 +0300 Subject: [PATCH 08/16] feat: refactoring stuff --- benchmarks/CMakeLists.txt | 2 + benchmarks/support/query_runners.cpp | 435 +++++++ benchmarks/support/query_runners.h | 1304 +-------------------- benchmarks/support/query_runners_fast.cpp | 715 +++++++++++ 4 files changed, 1156 insertions(+), 1300 deletions(-) create mode 100644 benchmarks/support/query_runners.cpp create mode 100644 benchmarks/support/query_runners_fast.cpp diff --git a/benchmarks/CMakeLists.txt b/benchmarks/CMakeLists.txt index 7fe3cd2..c50aae0 100644 --- a/benchmarks/CMakeLists.txt +++ b/benchmarks/CMakeLists.txt @@ -10,6 +10,8 @@ target_link_libraries(benchmark_csv_to_columnar add_executable(benchmark_queries benchmark_queries.cpp support/query_result_checks.cpp + support/query_runners.cpp + support/query_runners_fast.cpp support/query_result_checks.h support/query_runners.h ) diff --git a/benchmarks/support/query_runners.cpp b/benchmarks/support/query_runners.cpp new file mode 100644 index 0000000..61dbbb8 --- /dev/null +++ b/benchmarks/support/query_runners.cpp @@ -0,0 +1,435 @@ +#include "query_runners.h" + +#include +#include +#include +#include +#include +#include + +#include "common/error.h" + +static ExprPtr MakeExpr(const ExprKind kind, std::string output_name = {}) { + auto expr = std::make_shared(); + expr->kind = kind; + expr->output_name = std::move(output_name); + return expr; +} + +static ExprPtr Col(std::string name) { + auto expr = MakeExpr(ExprKind::Column, name); + expr->column = ColumnRef{.qualifier = {}, .name = std::move(name)}; + return expr; +} + +static ExprPtr Num(std::string text) { + auto expr = MakeExpr(ExprKind::Literal, text); + expr->literal = QueryLiteral{.text = std::move(text), .kind = LiteralKind::Numeric}; + return expr; +} + +static ExprPtr Str(std::string text) { + auto expr = MakeExpr(ExprKind::Literal, text); + expr->literal = QueryLiteral{.text = std::move(text), .kind = LiteralKind::String}; + return expr; +} + +static ExprPtr Ident(std::string text) { + auto expr = MakeExpr(ExprKind::Literal, text); + expr->literal = QueryLiteral{.text = std::move(text), .kind = LiteralKind::Identifier}; + return expr; +} + +static ExprPtr Star() { return MakeExpr(ExprKind::Star, "*"); } + +static std::string FormatFunctionName(const std::string_view function, const std::vector& args) { + std::string out(function); + out += "("; + for (size_t i = 0; i < args.size(); ++i) { + if (i > 0) { + out += ", "; + } + out += args[i]->output_name; + } + out += ")"; + return out; +} + +static ExprPtr Func(std::string name, std::vector args, std::string output_name = {}) { + auto expr = MakeExpr(ExprKind::Function); + expr->function_name = std::move(name); + expr->arguments = std::move(args); + expr->output_name = + output_name.empty() ? FormatFunctionName(expr->function_name, expr->arguments) : std::move(output_name); + return expr; +} + +static ExprPtr ExtractMinute(ExprPtr expr) { + return Func("EXTRACT", {Ident("MINUTE"), std::move(expr)}, "EXTRACT(MINUTE, EventTime)"); +} + +static ExprPtr DateTruncMinute(ExprPtr expr) { + return Func("DATE_TRUNC", {Str("'minute'"), std::move(expr)}, "DATE_TRUNC('minute', EventTime)"); +} + +static ExprPtr Binary(ExprPtr left, const BinaryOp op, ExprPtr right) { + auto expr = MakeExpr(ExprKind::Binary); + expr->binary_op = op; + expr->output_name = left->output_name + (op == BinaryOp::Add ? " + " : " - ") + right->output_name; + expr->left = std::move(left); + expr->right = std::move(right); + return expr; +} + +static ExprPtr Add(ExprPtr left, ExprPtr right) { return Binary(std::move(left), BinaryOp::Add, std::move(right)); } + +static ExprPtr Sub(ExprPtr left, ExprPtr right) { + return Binary(std::move(left), BinaryOp::Subtract, std::move(right)); +} + +static ExprPtr Case(PredicatePtr condition, ExprPtr then_expr, ExprPtr else_expr) { + auto expr = MakeExpr(ExprKind::Case, "CASE"); + expr->case_spec = CaseSpec{ + .condition = std::move(condition), + .then_expr = std::move(then_expr), + .else_expr = std::move(else_expr), + }; + return expr; +} + +static PredicatePtr Pred(const PredicateKind kind) { + auto pred = std::make_shared(); + pred->kind = kind; + return pred; +} + +static PredicatePtr Cmp(ExprPtr left, const ComparisonKind comparison, ExprPtr right) { + auto pred = Pred(PredicateKind::Comparison); + pred->left = std::move(left); + pred->comparison = comparison; + pred->right = std::move(right); + return pred; +} + +static PredicatePtr Like(ExprPtr left, ExprPtr pattern, const bool negated = false) { + auto pred = Pred(negated ? PredicateKind::NotLike : PredicateKind::Like); + pred->left = std::move(left); + pred->right = std::move(pattern); + return pred; +} + +static PredicatePtr In(ExprPtr left, std::vector values) { + auto pred = Pred(PredicateKind::In); + pred->left = std::move(left); + pred->values = std::move(values); + return pred; +} + +static PredicatePtr And(PredicatePtr lhs, PredicatePtr rhs) { + auto pred = Pred(PredicateKind::And); + pred->lhs = std::move(lhs); + pred->rhs = std::move(rhs); + return pred; +} + +static PredicatePtr All(const std::initializer_list predicates) { + auto it = predicates.begin(); + PredicatePtr result = *it++; + for (; it != predicates.end(); ++it) { + result = And(std::move(result), *it); + } + return result; +} + +static SelectItemSpec Select(ExprPtr expr, std::string alias = {}) { + SelectItemSpec item; + item.kind = SelectItemKind::GroupKey; + item.column = expr->kind == ExprKind::Column ? expr->column : ColumnRef{}; + item.expression = std::move(expr); + item.output_name = alias.empty() ? item.expression->output_name : std::move(alias); + return item; +} + +static SelectItemSpec Agg(std::string function, ExprPtr argument, const bool distinct = false, std::string alias = {}) { + AggSpec aggregate; + aggregate.function_name = std::move(function); + aggregate.distinct = distinct; + aggregate.argument_kind = argument->kind == ExprKind::Star ? AggArgumentKind::Star : AggArgumentKind::Column; + + const std::string argument_name = argument->output_name; + if (aggregate.argument_kind == AggArgumentKind::Column) { + aggregate.argument = std::move(argument); + if (aggregate.argument->kind == ExprKind::Column) { + aggregate.column = aggregate.argument->column; + } + } + + aggregate.output_name = aggregate.function_name + "("; + if (aggregate.argument_kind == AggArgumentKind::Star) { + aggregate.output_name += "*"; + } else { + if (aggregate.distinct) { + aggregate.output_name += "DISTINCT "; + } + aggregate.output_name += argument_name; + } + aggregate.output_name += ")"; + + SelectItemSpec item; + item.kind = SelectItemKind::Aggregate; + item.aggregate = std::move(aggregate); + item.output_name = alias.empty() ? item.aggregate.output_name : std::move(alias); + + return item; +} + +static OrderBySpec Order(SelectItemSpec item, const bool descending = false) { + return OrderBySpec{.item = std::move(item), .descending = descending}; +} + +static OrderBySpec OrderExpr(ExprPtr expr, const bool descending = false) { + return Order(Select(std::move(expr)), descending); +} + +static OrderBySpec OrderAgg(std::string function, ExprPtr argument, const bool descending = false) { + return Order(Agg(std::move(function), std::move(argument)), descending); +} + +static Query Q(std::vector select_items, std::vector group_by = {}, PredicatePtr filter = {}, + std::vector order_by = {}, const std::optional limit = {}, const size_t offset = 0, + PredicatePtr having = {}) { + Query query; + query.table_name = "hits"; + query.select_items = std::move(select_items); + query.group_by = std::move(group_by); + query.filter = std::move(filter); + query.having = std::move(having); + query.order_by = std::move(order_by); + query.limit = limit; + query.offset = offset; + return query; +} + +static std::optional BuildClickBenchQuery(const int id) { + switch (id) { + case 0: + return Q({Agg("COUNT", Star())}); + case 1: + return Q({Agg("COUNT", Star())}, {}, Cmp(Col("AdvEngineID"), ComparisonKind::NotEqual, Num("0"))); + case 2: + return Q({Agg("SUM", Col("AdvEngineID")), Agg("COUNT", Star()), Agg("AVG", Col("ResolutionWidth"))}); + case 3: + return Q({Agg("AVG", Col("UserID"))}); + case 4: + return Q({Agg("COUNT", Col("UserID"), true)}); + case 5: + return Q({Agg("COUNT", Col("SearchPhrase"), true)}); + case 6: + return Q({Agg("MIN", Col("EventDate")), Agg("MAX", Col("EventDate"))}); + case 7: + return Q({Select(Col("AdvEngineID")), Agg("COUNT", Star())}, {Col("AdvEngineID")}, + Cmp(Col("AdvEngineID"), ComparisonKind::NotEqual, Num("0")), {OrderAgg("COUNT", Star(), true)}); + case 8: + return Q({Select(Col("RegionID")), Agg("COUNT", Col("UserID"), true, "u")}, {Col("RegionID")}, {}, + {OrderExpr(Col("u"), true)}, 10); + case 9: + return Q({Select(Col("RegionID")), Agg("SUM", Col("AdvEngineID")), Agg("COUNT", Star(), false, "c"), + Agg("AVG", Col("ResolutionWidth")), Agg("COUNT", Col("UserID"), true)}, + {Col("RegionID")}, {}, {OrderExpr(Col("c"), true)}, 10); + case 10: + return Q({Select(Col("MobilePhoneModel")), Agg("COUNT", Col("UserID"), true, "u")}, + {Col("MobilePhoneModel")}, Cmp(Col("MobilePhoneModel"), ComparisonKind::NotEqual, Str("''")), + {OrderExpr(Col("u"), true)}, 10); + case 11: + return Q( + {Select(Col("MobilePhone")), Select(Col("MobilePhoneModel")), Agg("COUNT", Col("UserID"), true, "u")}, + {Col("MobilePhone"), Col("MobilePhoneModel")}, + Cmp(Col("MobilePhoneModel"), ComparisonKind::NotEqual, Str("''")), {OrderExpr(Col("u"), true)}, 10); + case 12: + return Q({Select(Col("SearchPhrase")), Agg("COUNT", Star(), false, "c")}, {Col("SearchPhrase")}, + Cmp(Col("SearchPhrase"), ComparisonKind::NotEqual, Str("''")), {OrderExpr(Col("c"), true)}, 10); + case 13: + return Q({Select(Col("SearchPhrase")), Agg("COUNT", Col("UserID"), true, "u")}, {Col("SearchPhrase")}, + Cmp(Col("SearchPhrase"), ComparisonKind::NotEqual, Str("''")), {OrderExpr(Col("u"), true)}, 10); + case 14: + return Q({Select(Col("SearchEngineID")), Select(Col("SearchPhrase")), Agg("COUNT", Star(), false, "c")}, + {Col("SearchEngineID"), Col("SearchPhrase")}, + Cmp(Col("SearchPhrase"), ComparisonKind::NotEqual, Str("''")), {OrderExpr(Col("c"), true)}, 10); + case 15: + return Q({Select(Col("UserID")), Agg("COUNT", Star())}, {Col("UserID")}, {}, + {OrderAgg("COUNT", Star(), true)}, 10); + case 16: + return Q({Select(Col("UserID")), Select(Col("SearchPhrase")), Agg("COUNT", Star())}, + {Col("UserID"), Col("SearchPhrase")}, {}, {OrderAgg("COUNT", Star(), true)}, 10); + case 17: + return Q({Select(Col("UserID")), Select(Col("SearchPhrase")), Agg("COUNT", Star())}, + {Col("UserID"), Col("SearchPhrase")}, {}, {}, 10); + case 18: + return Q({Select(Col("UserID")), Select(ExtractMinute(Col("EventTime")), "m"), Select(Col("SearchPhrase")), + Agg("COUNT", Star())}, + {Col("UserID"), Col("m"), Col("SearchPhrase")}, {}, {OrderAgg("COUNT", Star(), true)}, 10); + case 19: + return Q({Select(Col("UserID"))}, {}, Cmp(Col("UserID"), ComparisonKind::Equal, Num("435090932899640449"))); + case 20: + return Q({Agg("COUNT", Star())}, {}, Like(Col("URL"), Str("'%google%'"))); + case 21: + return Q({Select(Col("SearchPhrase")), Agg("MIN", Col("URL")), Agg("COUNT", Star(), false, "c")}, + {Col("SearchPhrase")}, + All({Like(Col("URL"), Str("'%google%'")), + Cmp(Col("SearchPhrase"), ComparisonKind::NotEqual, Str("''"))}), + {OrderExpr(Col("c"), true)}, 10); + case 22: + return Q({Select(Col("SearchPhrase")), Agg("MIN", Col("URL")), Agg("MIN", Col("Title")), + Agg("COUNT", Star(), false, "c"), Agg("COUNT", Col("UserID"), true)}, + {Col("SearchPhrase")}, + All({Like(Col("Title"), Str("'%Google%'")), Like(Col("URL"), Str("'%.google.%'"), true), + Cmp(Col("SearchPhrase"), ComparisonKind::NotEqual, Str("''"))}), + {OrderExpr(Col("c"), true)}, 10); + case 23: + return Q({Select(Star())}, {}, Like(Col("URL"), Str("'%google%'")), {OrderExpr(Col("EventTime"))}, 10); + case 24: + return Q({Select(Col("SearchPhrase"))}, {}, Cmp(Col("SearchPhrase"), ComparisonKind::NotEqual, Str("''")), + {OrderExpr(Col("EventTime"))}, 10); + case 25: + return Q({Select(Col("SearchPhrase"))}, {}, Cmp(Col("SearchPhrase"), ComparisonKind::NotEqual, Str("''")), + {OrderExpr(Col("SearchPhrase"))}, 10); + case 26: + return Q({Select(Col("SearchPhrase"))}, {}, Cmp(Col("SearchPhrase"), ComparisonKind::NotEqual, Str("''")), + {OrderExpr(Col("EventTime")), OrderExpr(Col("SearchPhrase"))}, 10); + case 27: + return Q({Select(Col("CounterID")), Agg("AVG", Func("STRLEN", {Col("URL")}), false, "l"), + Agg("COUNT", Star(), false, "c")}, + {Col("CounterID")}, Cmp(Col("URL"), ComparisonKind::NotEqual, Str("''")), + {OrderExpr(Col("l"), true)}, 25, 0, Cmp(Col("c"), ComparisonKind::Greater, Num("100000"))); + case 28: + return Q({Select(Func("REGEXP_REPLACE", + {Col("Referer"), Str(R"('^https?://(?:www\.)?([^/]+)/.*$')"), Str(R"('\1')")}, + "REGEXP_REPLACE(Referer, '^https?://(?:www\\.)?([^/]+)/.*$', '\\1')"), + "k"), + Agg("AVG", Func("STRLEN", {Col("Referer")}), false, "l"), Agg("COUNT", Star(), false, "c"), + Agg("MIN", Col("Referer"))}, + {Col("k")}, Cmp(Col("Referer"), ComparisonKind::NotEqual, Str("''")), {OrderExpr(Col("l"), true)}, + 25, 0, Cmp(Col("c"), ComparisonKind::Greater, Num("100000"))); + case 29: { + std::vector items; + items.push_back(Agg("SUM", Col("ResolutionWidth"))); + for (int offset = 1; offset <= 89; ++offset) { + items.push_back(Agg("SUM", Add(Col("ResolutionWidth"), Num(std::to_string(offset))))); + } + return Q(std::move(items)); + } + case 30: + return Q({Select(Col("SearchEngineID")), Select(Col("ClientIP")), Agg("COUNT", Star(), false, "c"), + Agg("SUM", Col("IsRefresh")), Agg("AVG", Col("ResolutionWidth"))}, + {Col("SearchEngineID"), Col("ClientIP")}, + Cmp(Col("SearchPhrase"), ComparisonKind::NotEqual, Str("''")), {OrderExpr(Col("c"), true)}, 10); + case 31: + return Q({Select(Col("WatchID")), Select(Col("ClientIP")), Agg("COUNT", Star(), false, "c"), + Agg("SUM", Col("IsRefresh")), Agg("AVG", Col("ResolutionWidth"))}, + {Col("WatchID"), Col("ClientIP")}, Cmp(Col("SearchPhrase"), ComparisonKind::NotEqual, Str("''")), + {OrderExpr(Col("c"), true)}, 10); + case 32: + return Q({Select(Col("WatchID")), Select(Col("ClientIP")), Agg("COUNT", Star(), false, "c"), + Agg("SUM", Col("IsRefresh")), Agg("AVG", Col("ResolutionWidth"))}, + {Col("WatchID"), Col("ClientIP")}, {}, {OrderExpr(Col("c"), true)}, 10); + case 33: + return Q({Select(Col("URL")), Agg("COUNT", Star(), false, "c")}, {Col("URL")}, {}, + {OrderExpr(Col("c"), true)}, 10); + case 34: + return Q({Select(Num("1")), Select(Col("URL")), Agg("COUNT", Star(), false, "c")}, {Num("1"), Col("URL")}, + {}, {OrderExpr(Col("c"), true)}, 10); + case 35: + return Q({Select(Col("ClientIP")), Select(Sub(Col("ClientIP"), Num("1"))), + Select(Sub(Col("ClientIP"), Num("2"))), Select(Sub(Col("ClientIP"), Num("3"))), + Agg("COUNT", Star(), false, "c")}, + {Col("ClientIP"), Sub(Col("ClientIP"), Num("1")), Sub(Col("ClientIP"), Num("2")), + Sub(Col("ClientIP"), Num("3"))}, + {}, {OrderExpr(Col("c"), true)}, 10); + case 36: + return Q({Select(Col("URL")), Agg("COUNT", Star(), false, "PageViews")}, {Col("URL")}, + All({Cmp(Col("CounterID"), ComparisonKind::Equal, Num("62")), + Cmp(Col("EventDate"), ComparisonKind::GreaterOrEqual, Str("'2013-07-01'")), + Cmp(Col("EventDate"), ComparisonKind::LessOrEqual, Str("'2013-07-31'")), + Cmp(Col("DontCountHits"), ComparisonKind::Equal, Num("0")), + Cmp(Col("IsRefresh"), ComparisonKind::Equal, Num("0")), + Cmp(Col("URL"), ComparisonKind::NotEqual, Str("''"))}), + {OrderExpr(Col("PageViews"), true)}, 10); + case 37: + return Q({Select(Col("Title")), Agg("COUNT", Star(), false, "PageViews")}, {Col("Title")}, + All({Cmp(Col("CounterID"), ComparisonKind::Equal, Num("62")), + Cmp(Col("EventDate"), ComparisonKind::GreaterOrEqual, Str("'2013-07-01'")), + Cmp(Col("EventDate"), ComparisonKind::LessOrEqual, Str("'2013-07-31'")), + Cmp(Col("DontCountHits"), ComparisonKind::Equal, Num("0")), + Cmp(Col("IsRefresh"), ComparisonKind::Equal, Num("0")), + Cmp(Col("Title"), ComparisonKind::NotEqual, Str("''"))}), + {OrderExpr(Col("PageViews"), true)}, 10); + case 38: + return Q({Select(Col("URL")), Agg("COUNT", Star(), false, "PageViews")}, {Col("URL")}, + All({Cmp(Col("CounterID"), ComparisonKind::Equal, Num("62")), + Cmp(Col("EventDate"), ComparisonKind::GreaterOrEqual, Str("'2013-07-01'")), + Cmp(Col("EventDate"), ComparisonKind::LessOrEqual, Str("'2013-07-31'")), + Cmp(Col("IsRefresh"), ComparisonKind::Equal, Num("0")), + Cmp(Col("IsLink"), ComparisonKind::NotEqual, Num("0")), + Cmp(Col("IsDownload"), ComparisonKind::Equal, Num("0"))}), + {OrderExpr(Col("PageViews"), true)}, 10, 1000); + case 39: + return Q({Select(Col("TraficSourceID")), Select(Col("SearchEngineID")), Select(Col("AdvEngineID")), + Select(Case(All({Cmp(Col("SearchEngineID"), ComparisonKind::Equal, Num("0")), + Cmp(Col("AdvEngineID"), ComparisonKind::Equal, Num("0"))}), + Col("Referer"), Str("''")), + "Src"), + Select(Col("URL"), "Dst"), Agg("COUNT", Star(), false, "PageViews")}, + {Col("TraficSourceID"), Col("SearchEngineID"), Col("AdvEngineID"), Col("Src"), Col("Dst")}, + All({Cmp(Col("CounterID"), ComparisonKind::Equal, Num("62")), + Cmp(Col("EventDate"), ComparisonKind::GreaterOrEqual, Str("'2013-07-01'")), + Cmp(Col("EventDate"), ComparisonKind::LessOrEqual, Str("'2013-07-31'")), + Cmp(Col("IsRefresh"), ComparisonKind::Equal, Num("0"))}), + {OrderExpr(Col("PageViews"), true)}, 10, 1000); + case 40: + return Q({Select(Col("URLHash")), Select(Col("EventDate")), Agg("COUNT", Star(), false, "PageViews")}, + {Col("URLHash"), Col("EventDate")}, + All({Cmp(Col("CounterID"), ComparisonKind::Equal, Num("62")), + Cmp(Col("EventDate"), ComparisonKind::GreaterOrEqual, Str("'2013-07-01'")), + Cmp(Col("EventDate"), ComparisonKind::LessOrEqual, Str("'2013-07-31'")), + Cmp(Col("IsRefresh"), ComparisonKind::Equal, Num("0")), + In(Col("TraficSourceID"), {Num("-1"), Num("6")}), + Cmp(Col("RefererHash"), ComparisonKind::Equal, Num("3594120000172545465"))}), + {OrderExpr(Col("PageViews"), true)}, 10, 100); + case 41: + return Q({Select(Col("WindowClientWidth")), Select(Col("WindowClientHeight")), + Agg("COUNT", Star(), false, "PageViews")}, + {Col("WindowClientWidth"), Col("WindowClientHeight")}, + All({Cmp(Col("CounterID"), ComparisonKind::Equal, Num("62")), + Cmp(Col("EventDate"), ComparisonKind::GreaterOrEqual, Str("'2013-07-01'")), + Cmp(Col("EventDate"), ComparisonKind::LessOrEqual, Str("'2013-07-31'")), + Cmp(Col("IsRefresh"), ComparisonKind::Equal, Num("0")), + Cmp(Col("DontCountHits"), ComparisonKind::Equal, Num("0")), + Cmp(Col("URLHash"), ComparisonKind::Equal, Num("2868770270353813622"))}), + {OrderExpr(Col("PageViews"), true)}, 10, 10000); + case 42: + return Q({Select(DateTruncMinute(Col("EventTime")), "M"), Agg("COUNT", Star(), false, "PageViews")}, + {Col("M")}, + All({Cmp(Col("CounterID"), ComparisonKind::Equal, Num("62")), + Cmp(Col("EventDate"), ComparisonKind::GreaterOrEqual, Str("'2013-07-14'")), + Cmp(Col("EventDate"), ComparisonKind::LessOrEqual, Str("'2013-07-15'")), + Cmp(Col("IsRefresh"), ComparisonKind::Equal, Num("0")), + Cmp(Col("DontCountHits"), ComparisonKind::Equal, Num("0"))}), + {OrderExpr(Col("M"))}, 10, 1000); + default: + return std::nullopt; + } +} + +bool HasHardcodedClickBenchQuery(const int query_id) { return query_id >= 0 && query_id <= 42; } + +Query BuildRequiredClickBenchQuery(const int query_id) { + auto query = BuildClickBenchQuery(query_id); + if (!query.has_value()) { + throw Error::InvalidArgument("benchmark", "unknown ClickBench query id"); + } + return std::move(*query); +} + +ExecuteExpected RunHardcodedClickBenchQuery(const Executor& executor, const int query_id) { + return executor.Execute(BuildRequiredClickBenchQuery(query_id)); +} diff --git a/benchmarks/support/query_runners.h b/benchmarks/support/query_runners.h index 6971b5e..bf78315 100644 --- a/benchmarks/support/query_runners.h +++ b/benchmarks/support/query_runners.h @@ -1,1316 +1,20 @@ #pragma once #include -#include #include -#include -#include -#include -#include -#include -#include -#include "common/parsing.h" -#include "common/string_arena.h" #include "executor/executor.h" -#include "executor/operators_internal.h" #include "executor/query.h" -#include "executor/query_utils.h" -#include "io/columnar_batch.h" enum class QueryExecutionMode { Hardcoded, Parser, }; -namespace clickbench { - -inline ExprPtr MakeExpr(const ExprKind kind, std::string output_name = {}) { - auto expr = std::make_shared(); - expr->kind = kind; - expr->output_name = std::move(output_name); - return expr; -} - -inline ExprPtr Col(std::string name) { - auto expr = MakeExpr(ExprKind::Column, name); - expr->column = ColumnRef{.qualifier = {}, .name = std::move(name)}; - return expr; -} - -inline ExprPtr Num(std::string text) { - auto expr = MakeExpr(ExprKind::Literal, text); - expr->literal = QueryLiteral{.text = std::move(text), .kind = LiteralKind::Numeric}; - return expr; -} - -inline ExprPtr Str(std::string text) { - auto expr = MakeExpr(ExprKind::Literal, text); - expr->literal = QueryLiteral{.text = std::move(text), .kind = LiteralKind::String}; - return expr; -} - -inline ExprPtr Ident(std::string text) { - auto expr = MakeExpr(ExprKind::Literal, text); - expr->literal = QueryLiteral{.text = std::move(text), .kind = LiteralKind::Identifier}; - return expr; -} - -inline ExprPtr Star() { return MakeExpr(ExprKind::Star, "*"); } - -inline std::string FormatFunctionName(const std::string_view function, const std::vector& args) { - std::string out(function); - out += "("; - for (size_t i = 0; i < args.size(); ++i) { - if (i > 0) { - out += ", "; - } - out += args[i]->output_name; - } - out += ")"; - return out; -} - -inline ExprPtr Func(std::string name, std::vector args, std::string output_name = {}) { - auto expr = MakeExpr(ExprKind::Function); - expr->function_name = std::move(name); - expr->arguments = std::move(args); - expr->output_name = - output_name.empty() ? FormatFunctionName(expr->function_name, expr->arguments) : std::move(output_name); - return expr; -} - -inline ExprPtr ExtractMinute(ExprPtr expr) { - return Func("EXTRACT", {Ident("MINUTE"), std::move(expr)}, "EXTRACT(MINUTE, EventTime)"); -} - -inline ExprPtr DateTruncMinute(ExprPtr expr) { - return Func("DATE_TRUNC", {Str("'minute'"), std::move(expr)}, "DATE_TRUNC('minute', EventTime)"); -} - -inline ExprPtr Binary(ExprPtr left, const BinaryOp op, ExprPtr right) { - auto expr = MakeExpr(ExprKind::Binary); - expr->binary_op = op; - expr->output_name = left->output_name + (op == BinaryOp::Add ? " + " : " - ") + right->output_name; - expr->left = std::move(left); - expr->right = std::move(right); - return expr; -} - -inline ExprPtr Add(ExprPtr left, ExprPtr right) { return Binary(std::move(left), BinaryOp::Add, std::move(right)); } - -inline ExprPtr Sub(ExprPtr left, ExprPtr right) { - return Binary(std::move(left), BinaryOp::Subtract, std::move(right)); -} - -inline ExprPtr Case(PredicatePtr condition, ExprPtr then_expr, ExprPtr else_expr) { - auto expr = MakeExpr(ExprKind::Case, "CASE"); - expr->case_spec = CaseSpec{ - .condition = std::move(condition), - .then_expr = std::move(then_expr), - .else_expr = std::move(else_expr), - }; - return expr; -} - -inline PredicatePtr Pred(const PredicateKind kind) { - auto pred = std::make_shared(); - pred->kind = kind; - return pred; -} - -inline PredicatePtr Cmp(ExprPtr left, const ComparisonKind comparison, ExprPtr right) { - auto pred = Pred(PredicateKind::Comparison); - pred->left = std::move(left); - pred->comparison = comparison; - pred->right = std::move(right); - return pred; -} - -inline PredicatePtr Like(ExprPtr left, ExprPtr pattern, const bool negated = false) { - auto pred = Pred(negated ? PredicateKind::NotLike : PredicateKind::Like); - pred->left = std::move(left); - pred->right = std::move(pattern); - return pred; -} - -inline PredicatePtr In(ExprPtr left, std::vector values) { - auto pred = Pred(PredicateKind::In); - pred->left = std::move(left); - pred->values = std::move(values); - return pred; -} - -inline PredicatePtr And(PredicatePtr lhs, PredicatePtr rhs) { - auto pred = Pred(PredicateKind::And); - pred->lhs = std::move(lhs); - pred->rhs = std::move(rhs); - return pred; -} - -inline PredicatePtr All(const std::initializer_list predicates) { - auto it = predicates.begin(); - PredicatePtr result = *it++; - for (; it != predicates.end(); ++it) { - result = And(std::move(result), *it); - } - return result; -} - -inline SelectItemSpec Select(ExprPtr expr, std::string alias = {}) { - SelectItemSpec item; - item.kind = SelectItemKind::GroupKey; - item.column = expr->kind == ExprKind::Column ? expr->column : ColumnRef{}; - item.expression = std::move(expr); - item.output_name = alias.empty() ? item.expression->output_name : std::move(alias); - return item; -} - -inline SelectItemSpec Agg(std::string function, ExprPtr argument, const bool distinct = false, std::string alias = {}) { - AggSpec aggregate; - aggregate.function_name = std::move(function); - aggregate.distinct = distinct; - aggregate.argument_kind = argument->kind == ExprKind::Star ? AggArgumentKind::Star : AggArgumentKind::Column; - - const std::string argument_name = argument->output_name; - if (aggregate.argument_kind == AggArgumentKind::Column) { - aggregate.argument = std::move(argument); - if (aggregate.argument->kind == ExprKind::Column) { - aggregate.column = aggregate.argument->column; - } - } - - aggregate.output_name = aggregate.function_name + "("; - if (aggregate.argument_kind == AggArgumentKind::Star) { - aggregate.output_name += "*"; - } else { - if (aggregate.distinct) { - aggregate.output_name += "DISTINCT "; - } - aggregate.output_name += argument_name; - } - aggregate.output_name += ")"; - - SelectItemSpec item; - item.kind = SelectItemKind::Aggregate; - item.aggregate = std::move(aggregate); - item.output_name = alias.empty() ? item.aggregate.output_name : std::move(alias); - - return item; -} - -inline OrderBySpec Order(SelectItemSpec item, const bool descending = false) { - return OrderBySpec{.item = std::move(item), .descending = descending}; -} - -inline OrderBySpec OrderExpr(ExprPtr expr, const bool descending = false) { - return Order(Select(std::move(expr)), descending); -} - -inline OrderBySpec OrderAgg(std::string function, ExprPtr argument, const bool descending = false) { - return Order(Agg(std::move(function), std::move(argument)), descending); -} - -inline Query Q(std::vector select_items, std::vector group_by = {}, PredicatePtr filter = {}, - std::vector order_by = {}, const std::optional limit = {}, const size_t offset = 0, - PredicatePtr having = {}) { - Query query; - query.table_name = "hits"; - query.select_items = std::move(select_items); - query.group_by = std::move(group_by); - query.filter = std::move(filter); - query.having = std::move(having); - query.order_by = std::move(order_by); - query.limit = limit; - query.offset = offset; - return query; -} - -inline std::optional BuildClickBenchQuery(const int id) { - switch (id) { - case 0: - return Q({Agg("COUNT", Star())}); - case 1: - return Q({Agg("COUNT", Star())}, {}, Cmp(Col("AdvEngineID"), ComparisonKind::NotEqual, Num("0"))); - case 2: - return Q({Agg("SUM", Col("AdvEngineID")), Agg("COUNT", Star()), Agg("AVG", Col("ResolutionWidth"))}); - case 3: - return Q({Agg("AVG", Col("UserID"))}); - case 4: - return Q({Agg("COUNT", Col("UserID"), true)}); - case 5: - return Q({Agg("COUNT", Col("SearchPhrase"), true)}); - case 6: - return Q({Agg("MIN", Col("EventDate")), Agg("MAX", Col("EventDate"))}); - case 7: - return Q({Select(Col("AdvEngineID")), Agg("COUNT", Star())}, {Col("AdvEngineID")}, - Cmp(Col("AdvEngineID"), ComparisonKind::NotEqual, Num("0")), {OrderAgg("COUNT", Star(), true)}); - case 8: - return Q({Select(Col("RegionID")), Agg("COUNT", Col("UserID"), true, "u")}, {Col("RegionID")}, {}, - {OrderExpr(Col("u"), true)}, 10); - case 9: - return Q({Select(Col("RegionID")), Agg("SUM", Col("AdvEngineID")), Agg("COUNT", Star(), false, "c"), - Agg("AVG", Col("ResolutionWidth")), Agg("COUNT", Col("UserID"), true)}, - {Col("RegionID")}, {}, {OrderExpr(Col("c"), true)}, 10); - case 10: - return Q({Select(Col("MobilePhoneModel")), Agg("COUNT", Col("UserID"), true, "u")}, - {Col("MobilePhoneModel")}, Cmp(Col("MobilePhoneModel"), ComparisonKind::NotEqual, Str("''")), - {OrderExpr(Col("u"), true)}, 10); - case 11: - return Q( - {Select(Col("MobilePhone")), Select(Col("MobilePhoneModel")), Agg("COUNT", Col("UserID"), true, "u")}, - {Col("MobilePhone"), Col("MobilePhoneModel")}, - Cmp(Col("MobilePhoneModel"), ComparisonKind::NotEqual, Str("''")), {OrderExpr(Col("u"), true)}, 10); - case 12: - return Q({Select(Col("SearchPhrase")), Agg("COUNT", Star(), false, "c")}, {Col("SearchPhrase")}, - Cmp(Col("SearchPhrase"), ComparisonKind::NotEqual, Str("''")), {OrderExpr(Col("c"), true)}, 10); - case 13: - return Q({Select(Col("SearchPhrase")), Agg("COUNT", Col("UserID"), true, "u")}, {Col("SearchPhrase")}, - Cmp(Col("SearchPhrase"), ComparisonKind::NotEqual, Str("''")), {OrderExpr(Col("u"), true)}, 10); - case 14: - return Q({Select(Col("SearchEngineID")), Select(Col("SearchPhrase")), Agg("COUNT", Star(), false, "c")}, - {Col("SearchEngineID"), Col("SearchPhrase")}, - Cmp(Col("SearchPhrase"), ComparisonKind::NotEqual, Str("''")), {OrderExpr(Col("c"), true)}, 10); - case 15: - return Q({Select(Col("UserID")), Agg("COUNT", Star())}, {Col("UserID")}, {}, - {OrderAgg("COUNT", Star(), true)}, 10); - case 16: - return Q({Select(Col("UserID")), Select(Col("SearchPhrase")), Agg("COUNT", Star())}, - {Col("UserID"), Col("SearchPhrase")}, {}, {OrderAgg("COUNT", Star(), true)}, 10); - case 17: - return Q({Select(Col("UserID")), Select(Col("SearchPhrase")), Agg("COUNT", Star())}, - {Col("UserID"), Col("SearchPhrase")}, {}, {}, 10); - case 18: - return Q({Select(Col("UserID")), Select(ExtractMinute(Col("EventTime")), "m"), Select(Col("SearchPhrase")), - Agg("COUNT", Star())}, - {Col("UserID"), Col("m"), Col("SearchPhrase")}, {}, {OrderAgg("COUNT", Star(), true)}, 10); - case 19: - return Q({Select(Col("UserID"))}, {}, Cmp(Col("UserID"), ComparisonKind::Equal, Num("435090932899640449"))); - case 20: - return Q({Agg("COUNT", Star())}, {}, Like(Col("URL"), Str("'%google%'"))); - case 21: - return Q({Select(Col("SearchPhrase")), Agg("MIN", Col("URL")), Agg("COUNT", Star(), false, "c")}, - {Col("SearchPhrase")}, - All({Like(Col("URL"), Str("'%google%'")), - Cmp(Col("SearchPhrase"), ComparisonKind::NotEqual, Str("''"))}), - {OrderExpr(Col("c"), true)}, 10); - case 22: - return Q({Select(Col("SearchPhrase")), Agg("MIN", Col("URL")), Agg("MIN", Col("Title")), - Agg("COUNT", Star(), false, "c"), Agg("COUNT", Col("UserID"), true)}, - {Col("SearchPhrase")}, - All({Like(Col("Title"), Str("'%Google%'")), Like(Col("URL"), Str("'%.google.%'"), true), - Cmp(Col("SearchPhrase"), ComparisonKind::NotEqual, Str("''"))}), - {OrderExpr(Col("c"), true)}, 10); - case 23: - return Q({Select(Star())}, {}, Like(Col("URL"), Str("'%google%'")), {OrderExpr(Col("EventTime"))}, 10); - case 24: - return Q({Select(Col("SearchPhrase"))}, {}, Cmp(Col("SearchPhrase"), ComparisonKind::NotEqual, Str("''")), - {OrderExpr(Col("EventTime"))}, 10); - case 25: - return Q({Select(Col("SearchPhrase"))}, {}, Cmp(Col("SearchPhrase"), ComparisonKind::NotEqual, Str("''")), - {OrderExpr(Col("SearchPhrase"))}, 10); - case 26: - return Q({Select(Col("SearchPhrase"))}, {}, Cmp(Col("SearchPhrase"), ComparisonKind::NotEqual, Str("''")), - {OrderExpr(Col("EventTime")), OrderExpr(Col("SearchPhrase"))}, 10); - case 27: - return Q({Select(Col("CounterID")), Agg("AVG", Func("STRLEN", {Col("URL")}), false, "l"), - Agg("COUNT", Star(), false, "c")}, - {Col("CounterID")}, Cmp(Col("URL"), ComparisonKind::NotEqual, Str("''")), - {OrderExpr(Col("l"), true)}, 25, 0, Cmp(Col("c"), ComparisonKind::Greater, Num("100000"))); - case 28: - return Q({Select(Func("REGEXP_REPLACE", - {Col("Referer"), Str(R"('^https?://(?:www\.)?([^/]+)/.*$')"), Str(R"('\1')")}, - "REGEXP_REPLACE(Referer, '^https?://(?:www\\.)?([^/]+)/.*$', '\\1')"), - "k"), - Agg("AVG", Func("STRLEN", {Col("Referer")}), false, "l"), Agg("COUNT", Star(), false, "c"), - Agg("MIN", Col("Referer"))}, - {Col("k")}, Cmp(Col("Referer"), ComparisonKind::NotEqual, Str("''")), {OrderExpr(Col("l"), true)}, - 25, 0, Cmp(Col("c"), ComparisonKind::Greater, Num("100000"))); - case 29: { - std::vector items; - items.push_back(Agg("SUM", Col("ResolutionWidth"))); - for (int offset = 1; offset <= 89; ++offset) { - items.push_back(Agg("SUM", Add(Col("ResolutionWidth"), Num(std::to_string(offset))))); - } - return Q(std::move(items)); - } - case 30: - return Q({Select(Col("SearchEngineID")), Select(Col("ClientIP")), Agg("COUNT", Star(), false, "c"), - Agg("SUM", Col("IsRefresh")), Agg("AVG", Col("ResolutionWidth"))}, - {Col("SearchEngineID"), Col("ClientIP")}, - Cmp(Col("SearchPhrase"), ComparisonKind::NotEqual, Str("''")), {OrderExpr(Col("c"), true)}, 10); - case 31: - return Q({Select(Col("WatchID")), Select(Col("ClientIP")), Agg("COUNT", Star(), false, "c"), - Agg("SUM", Col("IsRefresh")), Agg("AVG", Col("ResolutionWidth"))}, - {Col("WatchID"), Col("ClientIP")}, Cmp(Col("SearchPhrase"), ComparisonKind::NotEqual, Str("''")), - {OrderExpr(Col("c"), true)}, 10); - case 32: - return Q({Select(Col("WatchID")), Select(Col("ClientIP")), Agg("COUNT", Star(), false, "c"), - Agg("SUM", Col("IsRefresh")), Agg("AVG", Col("ResolutionWidth"))}, - {Col("WatchID"), Col("ClientIP")}, {}, {OrderExpr(Col("c"), true)}, 10); - case 33: - return Q({Select(Col("URL")), Agg("COUNT", Star(), false, "c")}, {Col("URL")}, {}, - {OrderExpr(Col("c"), true)}, 10); - case 34: - return Q({Select(Num("1")), Select(Col("URL")), Agg("COUNT", Star(), false, "c")}, {Num("1"), Col("URL")}, - {}, {OrderExpr(Col("c"), true)}, 10); - case 35: - return Q({Select(Col("ClientIP")), Select(Sub(Col("ClientIP"), Num("1"))), - Select(Sub(Col("ClientIP"), Num("2"))), Select(Sub(Col("ClientIP"), Num("3"))), - Agg("COUNT", Star(), false, "c")}, - {Col("ClientIP"), Sub(Col("ClientIP"), Num("1")), Sub(Col("ClientIP"), Num("2")), - Sub(Col("ClientIP"), Num("3"))}, - {}, {OrderExpr(Col("c"), true)}, 10); - case 36: - return Q({Select(Col("URL")), Agg("COUNT", Star(), false, "PageViews")}, {Col("URL")}, - All({Cmp(Col("CounterID"), ComparisonKind::Equal, Num("62")), - Cmp(Col("EventDate"), ComparisonKind::GreaterOrEqual, Str("'2013-07-01'")), - Cmp(Col("EventDate"), ComparisonKind::LessOrEqual, Str("'2013-07-31'")), - Cmp(Col("DontCountHits"), ComparisonKind::Equal, Num("0")), - Cmp(Col("IsRefresh"), ComparisonKind::Equal, Num("0")), - Cmp(Col("URL"), ComparisonKind::NotEqual, Str("''"))}), - {OrderExpr(Col("PageViews"), true)}, 10); - case 37: - return Q({Select(Col("Title")), Agg("COUNT", Star(), false, "PageViews")}, {Col("Title")}, - All({Cmp(Col("CounterID"), ComparisonKind::Equal, Num("62")), - Cmp(Col("EventDate"), ComparisonKind::GreaterOrEqual, Str("'2013-07-01'")), - Cmp(Col("EventDate"), ComparisonKind::LessOrEqual, Str("'2013-07-31'")), - Cmp(Col("DontCountHits"), ComparisonKind::Equal, Num("0")), - Cmp(Col("IsRefresh"), ComparisonKind::Equal, Num("0")), - Cmp(Col("Title"), ComparisonKind::NotEqual, Str("''"))}), - {OrderExpr(Col("PageViews"), true)}, 10); - case 38: - return Q({Select(Col("URL")), Agg("COUNT", Star(), false, "PageViews")}, {Col("URL")}, - All({Cmp(Col("CounterID"), ComparisonKind::Equal, Num("62")), - Cmp(Col("EventDate"), ComparisonKind::GreaterOrEqual, Str("'2013-07-01'")), - Cmp(Col("EventDate"), ComparisonKind::LessOrEqual, Str("'2013-07-31'")), - Cmp(Col("IsRefresh"), ComparisonKind::Equal, Num("0")), - Cmp(Col("IsLink"), ComparisonKind::NotEqual, Num("0")), - Cmp(Col("IsDownload"), ComparisonKind::Equal, Num("0"))}), - {OrderExpr(Col("PageViews"), true)}, 10, 1000); - case 39: - return Q({Select(Col("TraficSourceID")), Select(Col("SearchEngineID")), Select(Col("AdvEngineID")), - Select(Case(All({Cmp(Col("SearchEngineID"), ComparisonKind::Equal, Num("0")), - Cmp(Col("AdvEngineID"), ComparisonKind::Equal, Num("0"))}), - Col("Referer"), Str("''")), - "Src"), - Select(Col("URL"), "Dst"), Agg("COUNT", Star(), false, "PageViews")}, - {Col("TraficSourceID"), Col("SearchEngineID"), Col("AdvEngineID"), Col("Src"), Col("Dst")}, - All({Cmp(Col("CounterID"), ComparisonKind::Equal, Num("62")), - Cmp(Col("EventDate"), ComparisonKind::GreaterOrEqual, Str("'2013-07-01'")), - Cmp(Col("EventDate"), ComparisonKind::LessOrEqual, Str("'2013-07-31'")), - Cmp(Col("IsRefresh"), ComparisonKind::Equal, Num("0"))}), - {OrderExpr(Col("PageViews"), true)}, 10, 1000); - case 40: - return Q({Select(Col("URLHash")), Select(Col("EventDate")), Agg("COUNT", Star(), false, "PageViews")}, - {Col("URLHash"), Col("EventDate")}, - All({Cmp(Col("CounterID"), ComparisonKind::Equal, Num("62")), - Cmp(Col("EventDate"), ComparisonKind::GreaterOrEqual, Str("'2013-07-01'")), - Cmp(Col("EventDate"), ComparisonKind::LessOrEqual, Str("'2013-07-31'")), - Cmp(Col("IsRefresh"), ComparisonKind::Equal, Num("0")), - In(Col("TraficSourceID"), {Num("-1"), Num("6")}), - Cmp(Col("RefererHash"), ComparisonKind::Equal, Num("3594120000172545465"))}), - {OrderExpr(Col("PageViews"), true)}, 10, 100); - case 41: - return Q({Select(Col("WindowClientWidth")), Select(Col("WindowClientHeight")), - Agg("COUNT", Star(), false, "PageViews")}, - {Col("WindowClientWidth"), Col("WindowClientHeight")}, - All({Cmp(Col("CounterID"), ComparisonKind::Equal, Num("62")), - Cmp(Col("EventDate"), ComparisonKind::GreaterOrEqual, Str("'2013-07-01'")), - Cmp(Col("EventDate"), ComparisonKind::LessOrEqual, Str("'2013-07-31'")), - Cmp(Col("IsRefresh"), ComparisonKind::Equal, Num("0")), - Cmp(Col("DontCountHits"), ComparisonKind::Equal, Num("0")), - Cmp(Col("URLHash"), ComparisonKind::Equal, Num("2868770270353813622"))}), - {OrderExpr(Col("PageViews"), true)}, 10, 10000); - case 42: - return Q({Select(DateTruncMinute(Col("EventTime")), "M"), Agg("COUNT", Star(), false, "PageViews")}, - {Col("M")}, - All({Cmp(Col("CounterID"), ComparisonKind::Equal, Num("62")), - Cmp(Col("EventDate"), ComparisonKind::GreaterOrEqual, Str("'2013-07-14'")), - Cmp(Col("EventDate"), ComparisonKind::LessOrEqual, Str("'2013-07-15'")), - Cmp(Col("IsRefresh"), ComparisonKind::Equal, Num("0")), - Cmp(Col("DontCountHits"), ComparisonKind::Equal, Num("0"))}), - {OrderExpr(Col("M"))}, 10, 1000); - default: - return std::nullopt; - } -} - -} // namespace clickbench - namespace clickbench_fast { - -struct Int128Hash { - size_t operator()(const Int128 value) const noexcept { - const UInt128 raw = static_cast(value); - const uint64_t low = static_cast(raw); - const uint64_t high = static_cast(raw >> 64); - return std::hash{}(low ^ (high + 0x9e3779b97f4a7c15ULL + (low << 6) + (low >> 2))); - } -}; - -struct Int128PairHash { - size_t operator()(const std::pair& value) const noexcept { - const size_t lhs = Int128Hash{}(value.first); - const size_t rhs = Int128Hash{}(value.second); - return lhs ^ (rhs + 0x9e3779b97f4a7c15ULL + (lhs << 6) + (lhs >> 2)); - } -}; - -inline Batch SingleRow(const Schema& schema, const std::initializer_list values) { - Batch batch(schema, 1); - size_t column = 0; - - for (const std::string_view value : values) { - batch.AppendValueFromString(column++, value); - } - - return batch; -} - -inline std::unique_ptr ScanColumns(const std::filesystem::path& path, const Schema& schema, - const std::initializer_list columns) { - std::vector projection_indexes; - projection_indexes.reserve(columns.size()); - - for (const std::string_view column : columns) { - projection_indexes.push_back(FindColumnIndex(schema, column)); - } - - return CreateScanOperator(path, std::move(projection_indexes), {}); -} - -inline ExecuteExpected CountAll(const std::filesystem::path& path) { - const ColumnarBatchReader reader(path); - uint64_t rows = 0; - - for (const auto& row_group : reader.GetMetadata().row_groups) { - rows += row_group.row_count; - } - - return SingleRow(Schema{{ColumnSchema("COUNT(*)", ColumnType::Int64)}}, {std::to_string(rows)}); -} - -inline ExecuteExpected CountAdvEngineNonZero(const std::filesystem::path& path) { - const ColumnarBatchReader reader(path); - const auto scan = ScanColumns(path, reader.GetSchema(), {"AdvEngineID"}); - - uint64_t count = 0; - - while (const auto batch = scan->Next()) { - const Column& adv_engine = batch->ColumnAt(0); - for (size_t row = 0; row < batch->RowsCount(); ++row) { - if (adv_engine.ValueAsInt128(row) != 0) { - ++count; - } - } - } - - return SingleRow(Schema{{ColumnSchema("COUNT(*)", ColumnType::Int64)}}, {std::to_string(count)}); -} - -inline ExecuteExpected SumCountAvg(const std::filesystem::path& path) { - const ColumnarBatchReader reader(path); - const auto scan = ScanColumns(path, reader.GetSchema(), {"AdvEngineID", "ResolutionWidth"}); - - Int128 adv_sum = 0; - Int128 width_sum = 0; - uint64_t count = 0; - - while (const auto batch = scan->Next()) { - const Column& adv_engine = batch->ColumnAt(0); - const Column& width = batch->ColumnAt(1); - count += batch->RowsCount(); - for (size_t row = 0; row < batch->RowsCount(); ++row) { - adv_sum += adv_engine.ValueAsInt128(row); - width_sum += width.ValueAsInt128(row); - } - } - - return SingleRow( - Schema{{ColumnSchema("SUM(AdvEngineID)", ColumnType::Int128), ColumnSchema("COUNT(*)", ColumnType::Int64), - ColumnSchema("AVG(ResolutionWidth)", ColumnType::Int128)}}, - {Int128ToString(adv_sum), std::to_string(count), - count == 0 ? "0" : Int128ToString(width_sum / static_cast(count))}); -} - -inline ExecuteExpected AvgUserId(const std::filesystem::path& path) { - const ColumnarBatchReader reader(path); - const auto scan = ScanColumns(path, reader.GetSchema(), {"UserID"}); - - Int128 sum = 0; - uint64_t count = 0; - - while (const auto batch = scan->Next()) { - const Column& user_id = batch->ColumnAt(0); - count += batch->RowsCount(); - for (size_t row = 0; row < batch->RowsCount(); ++row) { - sum += user_id.ValueAsInt128(row); - } - } - - return SingleRow(Schema{{ColumnSchema("AVG(UserID)", ColumnType::Int128)}}, - {count == 0 ? "0" : Int128ToString(sum / static_cast(count))}); -} - -inline ExecuteExpected CountDistinctColumn(const std::filesystem::path& path, const std::string_view column_name, - const std::string_view output_name) { - const ColumnarBatchReader reader(path); - const auto scan = ScanColumns(path, reader.GetSchema(), {column_name}); - - std::unordered_set values; - - while (const auto batch = scan->Next()) { - const Column& column = batch->ColumnAt(0); - for (size_t row = 0; row < batch->RowsCount(); ++row) { - values.insert(column.ValueAsString(row)); - } - } - - return SingleRow(Schema{{ColumnSchema(std::string(output_name), ColumnType::Int64)}}, - {std::to_string(values.size())}); -} - -inline ExecuteExpected MinMaxEventDate(const std::filesystem::path& path) { - const ColumnarBatchReader reader(path); - const size_t event_date = FindColumnIndex(reader.GetSchema(), "EventDate"); - - bool has_value = false; - Int128 min_value = 0; - Int128 max_value = 0; - - for (const auto& row_group : reader.GetMetadata().row_groups) { - const auto& chunk = row_group.columns[event_date]; - if (!chunk.has_min_max) { - continue; - } - if (!has_value) { - min_value = chunk.min_value; - max_value = chunk.max_value; - has_value = true; - } else { - min_value = std::min(min_value, chunk.min_value); - max_value = std::max(max_value, chunk.max_value); - } - } - - return SingleRow( - Schema{{ColumnSchema("MIN(EventDate)", ColumnType::Date), ColumnSchema("MAX(EventDate)", ColumnType::Date)}}, - {DateToString(static_cast(min_value)), DateToString(static_cast(max_value))}); -} - -inline ExecuteExpected CountUrlContainsGoogle(const std::filesystem::path& path) { - const ColumnarBatchReader reader(path); - const auto scan = ScanColumns(path, reader.GetSchema(), {"URL"}); - - uint64_t count = 0; - - while (const auto batch = scan->Next()) { - const Column& url = batch->ColumnAt(0); - for (size_t row = 0; row < batch->RowsCount(); ++row) { - if (url.ValueAsString(row).find("google") != std::string::npos) { - ++count; - } - } - } - - return SingleRow(Schema{{ColumnSchema("COUNT(*)", ColumnType::Int64)}}, {std::to_string(count)}); -} - -inline bool Contains(const std::string_view haystack, const std::string_view needle) { - return haystack.find(needle) != std::string_view::npos; -} - -inline bool IsEmptyString(const std::string_view value) { return value.empty(); } - -inline std::string ExtractRefererHost(std::string_view referer) { - if (referer.empty()) { - return {}; - } - - if (referer.rfind("http://", 0) == 0) { - referer.remove_prefix(7); - } else if (referer.rfind("https://", 0) == 0) { - referer.remove_prefix(8); - } - - if (referer.rfind("www.", 0) == 0) { - referer.remove_prefix(4); - } - - const size_t slash = referer.find('/'); - - return std::string(referer.substr(0, slash)); -} - -struct SearchPhraseGroup { - std::string_view key; - uint64_t count = 0; - std::string_view min_url; - std::string_view min_title; - std::unordered_set distinct_users; - size_t ordinal = 0; - bool has_min_url = false; - bool has_min_title = false; -}; - -inline ExecuteExpected SearchPhraseGroupByUrlContainsGoogle(const std::filesystem::path& path) { - const ColumnarBatchReader reader(path); - auto scan = ScanColumns(path, reader.GetSchema(), {"URL", "SearchPhrase"}); - - StringArena arena; - std::unordered_map groups; - size_t ordinal = 0; - - while (auto batch = scan->Next()) { - const Column& url = batch->ColumnAt(0); - const Column& search_phrase = batch->ColumnAt(1); - for (size_t row = 0; row < batch->RowsCount(); ++row) { - if (!Contains(url.ValueAsString(row), "google")) { - continue; - } - - const std::string_view key = arena.Store(search_phrase.ValueAsString(row)); - if (key.empty()) { - continue; - } - - auto& group = groups[key]; - if (group.count == 0) { - group.key = key; - group.ordinal = ordinal++; - group.min_url = arena.Store(url.ValueAsString(row)); - group.has_min_url = true; - } else if (url.ValueAsString(row) < group.min_url) { - group.min_url = arena.Store(url.ValueAsString(row)); - } - - ++group.count; - } - } - - std::vector ordered; - ordered.reserve(groups.size()); - - for (const auto& val : groups | std::views::values) { - ordered.push_back(&val); - } - - std::ranges::sort(ordered, [](const SearchPhraseGroup* lhs, const SearchPhraseGroup* rhs) { - if (lhs->count != rhs->count) { - return lhs->count > rhs->count; - } - return lhs->ordinal < rhs->ordinal; - }); - - if (ordered.size() > 10) { - ordered.resize(10); - } - - Batch result(Schema{{ColumnSchema("SearchPhrase", ColumnType::String), ColumnSchema("MIN(URL)", ColumnType::String), - ColumnSchema("c", ColumnType::Int64)}}, - ordered.size()); - - for (const SearchPhraseGroup* group : ordered) { - result.AppendValueFromString(0, group->key); - result.AppendValueFromString(1, group->min_url); - result.AppendValueFromString(2, std::to_string(group->count)); - } - - return result; -} - -struct SearchPhraseStats { - std::string_view key; - uint64_t count = 0; - std::string_view min_url; - std::string_view min_title; - std::unordered_set distinct_users; - size_t ordinal = 0; -}; - -inline ExecuteExpected SearchPhraseGroupByTitleGoogle(const std::filesystem::path& path) { - const ColumnarBatchReader reader(path); - auto scan = ScanColumns(path, reader.GetSchema(), {"Title", "URL", "SearchPhrase", "UserID"}); - - StringArena arena; - std::unordered_map groups; - size_t ordinal = 0; - - while (auto batch = scan->Next()) { - const Column& title = batch->ColumnAt(0); - const Column& url = batch->ColumnAt(1); - const Column& search_phrase = batch->ColumnAt(2); - const Column& user_id = batch->ColumnAt(3); - for (size_t row = 0; row < batch->RowsCount(); ++row) { - const std::string_view title_value = arena.Store(title.ValueAsString(row)); - if (!Contains(title_value, "Google")) { - continue; - } - - const std::string_view url_value = arena.Store(url.ValueAsString(row)); - if (Contains(url_value, ".google.")) { - continue; - } - - const std::string_view key = arena.Store(search_phrase.ValueAsString(row)); - if (key.empty()) { - continue; - } - - auto& group = groups[key]; - if (group.count == 0) { - group.key = key; - group.ordinal = ordinal++; - group.min_url = url_value; - group.min_title = title_value; - } else { - if (url_value < group.min_url) { - group.min_url = url_value; - } - if (title_value < group.min_title) { - group.min_title = title_value; - } - } - - group.distinct_users.insert(user_id.ValueAsInt128(row)); - ++group.count; - } - } - - std::vector ordered; - ordered.reserve(groups.size()); - - for (const auto& val : groups | std::views::values) { - ordered.push_back(&val); - } - - std::ranges::sort(ordered, [](const SearchPhraseStats* lhs, const SearchPhraseStats* rhs) { - if (lhs->count != rhs->count) { - return lhs->count > rhs->count; - } - return lhs->ordinal < rhs->ordinal; - }); - - if (ordered.size() > 10) { - ordered.resize(10); - } - - Batch result(Schema{{ColumnSchema("SearchPhrase", ColumnType::String), ColumnSchema("MIN(URL)", ColumnType::String), - ColumnSchema("MIN(Title)", ColumnType::String), ColumnSchema("c", ColumnType::Int64), - ColumnSchema("COUNT(DISTINCT UserID)", ColumnType::Int64)}}, - ordered.size()); - - for (const SearchPhraseStats* group : ordered) { - result.AppendValueFromString(0, group->key); - result.AppendValueFromString(1, group->min_url); - result.AppendValueFromString(2, group->min_title); - result.AppendValueFromString(3, std::to_string(group->count)); - result.AppendValueFromString(4, std::to_string(group->distinct_users.size())); - } - - return result; -} - -struct IntGroupStats { - Int128 key1 = 0; - Int128 key2 = 0; - uint64_t count = 0; - Int128 sum_refresh = 0; - Int128 sum_width = 0; - size_t ordinal = 0; -}; - -template -std::vector OrderGroupsByCount(const Map& groups, const size_t limit) { - std::vector ordered; - ordered.reserve(groups.size()); - - for (const auto& entry : groups) { - ordered.push_back(&entry.second); - } - - std::ranges::sort(ordered, [](const auto* lhs, const auto* rhs) { - if (lhs->count != rhs->count) { - return lhs->count > rhs->count; - } - return lhs->ordinal < rhs->ordinal; - }); - - if (ordered.size() > limit) { - ordered.resize(limit); - } - - return ordered; -} - -inline ExecuteExpected SumWidthVariants(const std::filesystem::path& path) { - const ColumnarBatchReader reader(path); - const auto scan = ScanColumns(path, reader.GetSchema(), {"ResolutionWidth"}); - - Int128 width_sum = 0; - uint64_t row_count = 0; - - while (const auto batch = scan->Next()) { - const Column& width = batch->ColumnAt(0); - row_count += batch->RowsCount(); - for (size_t row = 0; row < batch->RowsCount(); ++row) { - width_sum += width.ValueAsInt128(row); - } - } - - std::vector schema_columns; - std::vector values; - - schema_columns.emplace_back("SUM(ResolutionWidth)", ColumnType::Int128); - values.push_back(Int128ToString(width_sum)); - - for (int offset = 1; offset <= 89; ++offset) { - schema_columns.emplace_back("SUM(ResolutionWidth + " + std::to_string(offset) + ")", ColumnType::Int128); - values.push_back(Int128ToString(width_sum + static_cast(offset) * static_cast(row_count))); - } - - Batch result(Schema{schema_columns}, 1); - for (size_t i = 0; i < values.size(); ++i) { - result.AppendValueFromString(i, values[i]); - } - - return result; -} - -inline ExecuteExpected GroupBySearchEngineAndClientIP(const std::filesystem::path& path, - const bool filter_search_phrase, - const std::string_view filter_value, - const std::string_view output_group_name) { - const ColumnarBatchReader reader(path); - auto scan = ScanColumns(path, reader.GetSchema(), - {"SearchEngineID", "ClientIP", "IsRefresh", "ResolutionWidth", "SearchPhrase"}); - - std::unordered_map, IntGroupStats, Int128PairHash> groups; - size_t ordinal = 0; - - while (auto batch = scan->Next()) { - const Column& search_engine = batch->ColumnAt(0); - const Column& client_ip = batch->ColumnAt(1); - const Column& is_refresh = batch->ColumnAt(2); - const Column& width = batch->ColumnAt(3); - const Column& search_phrase = batch->ColumnAt(4); - for (size_t row = 0; row < batch->RowsCount(); ++row) { - if (filter_search_phrase && search_phrase.ValueAsString(row).empty() == false && - search_phrase.ValueAsString(row) != filter_value) { - continue; - } - if (filter_search_phrase && search_phrase.ValueAsString(row).empty()) { - continue; - } - - const std::pair key{search_engine.ValueAsInt128(row), client_ip.ValueAsInt128(row)}; - auto& group = groups[key]; - if (group.count == 0) { - group.key1 = key.first; - group.key2 = key.second; - group.ordinal = ordinal++; - } - - ++group.count; - group.sum_refresh += is_refresh.ValueAsInt128(row); - group.sum_width += width.ValueAsInt128(row); - } - } - - const auto ordered = OrderGroupsByCount(groups, 10); - Batch result( - Schema{{ColumnSchema(std::string(output_group_name) + "1", ColumnType::Int128), - ColumnSchema(std::string(output_group_name) + "2", ColumnType::Int128), - ColumnSchema("COUNT(*)", ColumnType::Int64), ColumnSchema("SUM(IsRefresh)", ColumnType::Int128), - ColumnSchema("AVG(ResolutionWidth)", ColumnType::Int128)}}, - ordered.size()); - - for (const IntGroupStats* group : ordered) { - result.AppendValueFromString(0, Int128ToString(group->key1)); - result.AppendValueFromString(1, Int128ToString(group->key2)); - result.AppendValueFromString(2, std::to_string(group->count)); - result.AppendValueFromString(3, Int128ToString(group->sum_refresh)); - result.AppendValueFromString( - 4, group->count == 0 ? "0" : Int128ToString(group->sum_width / static_cast(group->count))); - } - - return result; -} - -inline ExecuteExpected GroupByURLCount(const std::filesystem::path& path, const bool add_constant_column) { - const ColumnarBatchReader reader(path); - auto scan = ScanColumns(path, reader.GetSchema(), {"URL"}); - - StringArena arena; - std::unordered_map groups; - size_t ordinal = 0; - - while (auto batch = scan->Next()) { - const Column& url = batch->ColumnAt(0); - for (size_t row = 0; row < batch->RowsCount(); ++row) { - const std::string_view key = arena.Store(url.ValueAsString(row)); - auto& group = groups[key]; - if (group.count == 0) { - group.key = key; - group.ordinal = ordinal++; - } - ++group.count; - } - } - - const auto ordered = OrderGroupsByCount(groups, 10); - Batch result(add_constant_column - ? Schema{{ColumnSchema("1", ColumnType::Int64), ColumnSchema("URL", ColumnType::String), - ColumnSchema("COUNT(*)", ColumnType::Int64)}} - : Schema{{ColumnSchema("URL", ColumnType::String), ColumnSchema("COUNT(*)", ColumnType::Int64)}}, - ordered.size()); - - for (const SearchPhraseStats* group : ordered) { - if (add_constant_column) { - result.AppendValueFromString(0, "1"); - result.AppendValueFromString(1, group->key); - result.AppendValueFromString(2, std::to_string(group->count)); - } else { - result.AppendValueFromString(0, group->key); - result.AppendValueFromString(1, std::to_string(group->count)); - } - } - - return result; -} - -inline ExecuteExpected GroupBySearchEngineClientIP(const std::filesystem::path& path, const bool filter_search_phrase) { - const ColumnarBatchReader reader(path); - auto scan = ScanColumns(path, reader.GetSchema(), - {"SearchEngineID", "ClientIP", "IsRefresh", "ResolutionWidth", "SearchPhrase"}); - - std::unordered_map, IntGroupStats, Int128PairHash> groups; - size_t ordinal = 0; - - while (auto batch = scan->Next()) { - const Column& search_engine = batch->ColumnAt(0); - const Column& client_ip = batch->ColumnAt(1); - const Column& is_refresh = batch->ColumnAt(2); - const Column& width = batch->ColumnAt(3); - const Column& search_phrase = batch->ColumnAt(4); - for (size_t row = 0; row < batch->RowsCount(); ++row) { - if (filter_search_phrase && search_phrase.ValueAsString(row).empty()) { - continue; - } - - const std::pair key{search_engine.ValueAsInt128(row), client_ip.ValueAsInt128(row)}; - auto& group = groups[key]; - if (group.count == 0) { - group.key1 = key.first; - group.key2 = key.second; - group.ordinal = ordinal++; - } - - ++group.count; - group.sum_refresh += is_refresh.ValueAsInt128(row); - group.sum_width += width.ValueAsInt128(row); - } - } - - const auto ordered = OrderGroupsByCount(groups, 10); - Batch result( - Schema{{ColumnSchema("SearchEngineID", ColumnType::Int128), ColumnSchema("ClientIP", ColumnType::Int128), - ColumnSchema("c", ColumnType::Int64), ColumnSchema("SUM(IsRefresh)", ColumnType::Int128), - ColumnSchema("AVG(ResolutionWidth)", ColumnType::Int128)}}, - ordered.size()); - - for (const IntGroupStats* group : ordered) { - result.AppendValueFromString(0, Int128ToString(group->key1)); - result.AppendValueFromString(1, Int128ToString(group->key2)); - result.AppendValueFromString(2, std::to_string(group->count)); - result.AppendValueFromString(3, Int128ToString(group->sum_refresh)); - result.AppendValueFromString( - 4, group->count == 0 ? "0" : Int128ToString(group->sum_width / static_cast(group->count))); - } - - return result; -} - -inline ExecuteExpected GroupByWatchClientIP(const std::filesystem::path& path, const bool filter_search_phrase) { - const ColumnarBatchReader reader(path); - auto scan = - ScanColumns(path, reader.GetSchema(), {"WatchID", "ClientIP", "IsRefresh", "ResolutionWidth", "SearchPhrase"}); - - std::unordered_map, IntGroupStats, Int128PairHash> groups; - size_t ordinal = 0; - - while (auto batch = scan->Next()) { - const Column& watch_id = batch->ColumnAt(0); - const Column& client_ip = batch->ColumnAt(1); - const Column& is_refresh = batch->ColumnAt(2); - const Column& width = batch->ColumnAt(3); - const Column& search_phrase = batch->ColumnAt(4); - for (size_t row = 0; row < batch->RowsCount(); ++row) { - if (filter_search_phrase && search_phrase.ValueAsString(row).empty()) { - continue; - } - - const std::pair key{watch_id.ValueAsInt128(row), client_ip.ValueAsInt128(row)}; - auto& group = groups[key]; - if (group.count == 0) { - group.key1 = key.first; - group.key2 = key.second; - group.ordinal = ordinal++; - } - - ++group.count; - group.sum_refresh += is_refresh.ValueAsInt128(row); - group.sum_width += width.ValueAsInt128(row); - } - } - - const auto ordered = OrderGroupsByCount(groups, 10); - Batch result(Schema{{ColumnSchema("WatchID", ColumnType::Int128), ColumnSchema("ClientIP", ColumnType::Int128), - ColumnSchema("c", ColumnType::Int64), ColumnSchema("SUM(IsRefresh)", ColumnType::Int128), - ColumnSchema("AVG(ResolutionWidth)", ColumnType::Int128)}}, - ordered.size()); - - for (const IntGroupStats* group : ordered) { - result.AppendValueFromString(0, Int128ToString(group->key1)); - result.AppendValueFromString(1, Int128ToString(group->key2)); - result.AppendValueFromString(2, std::to_string(group->count)); - result.AppendValueFromString(3, Int128ToString(group->sum_refresh)); - result.AppendValueFromString( - 4, group->count == 0 ? "0" : Int128ToString(group->sum_width / static_cast(group->count))); - } - - return result; -} - -inline ExecuteExpected GroupByClientIPDerived(const std::filesystem::path& path) { - const ColumnarBatchReader reader(path); - const auto scan = ScanColumns(path, reader.GetSchema(), {"ClientIP"}); - - std::unordered_map groups; - size_t ordinal = 0; - - while (const auto batch = scan->Next()) { - const Column& client_ip = batch->ColumnAt(0); - for (size_t row = 0; row < batch->RowsCount(); ++row) { - const Int128 key = client_ip.ValueAsInt128(row); - auto& group = groups[key]; - if (group.count == 0) { - group.key1 = key; - group.ordinal = ordinal++; - } - ++group.count; - } - } - - const auto ordered = OrderGroupsByCount(groups, 10); - Batch result( - Schema{{ColumnSchema("ClientIP", ColumnType::Int128), ColumnSchema("ClientIP - 1", ColumnType::Int128), - ColumnSchema("ClientIP - 2", ColumnType::Int128), ColumnSchema("ClientIP - 3", ColumnType::Int128), - ColumnSchema("COUNT(*)", ColumnType::Int64)}}, - ordered.size()); - - for (const IntGroupStats* group : ordered) { - const Int128 key = group->key1; - result.AppendValueFromString(0, Int128ToString(key)); - result.AppendValueFromString(1, Int128ToString(key - 1)); - result.AppendValueFromString(2, Int128ToString(key - 2)); - result.AppendValueFromString(3, Int128ToString(key - 3)); - result.AppendValueFromString(4, std::to_string(group->count)); - } - - return result; -} - -struct SearchPhraseOrderRow { - Int128 event_time = 0; - std::string_view search_phrase; - size_t ordinal = 0; -}; - -inline ExecuteExpected SelectSearchPhraseOrderByEventTime(const std::filesystem::path& path) { - const ColumnarBatchReader reader(path); - const auto scan = ScanColumns(path, reader.GetSchema(), {"EventTime", "SearchPhrase"}); - - StringArena arena; - std::vector rows; - size_t ordinal = 0; - - while (const auto batch = scan->Next()) { - const Column& event_time = batch->ColumnAt(0); - const Column& search_phrase = batch->ColumnAt(1); - for (size_t row = 0; row < batch->RowsCount(); ++row) { - const std::string_view phrase = arena.Store(search_phrase.ValueAsString(row)); - if (phrase.empty()) { - continue; - } - rows.push_back(SearchPhraseOrderRow{ - .event_time = event_time.ValueAsInt128(row), .search_phrase = phrase, .ordinal = ordinal++}); - } - } - - std::ranges::sort(rows, [](const SearchPhraseOrderRow& lhs, const SearchPhraseOrderRow& rhs) { - if (lhs.event_time != rhs.event_time) { - return lhs.event_time < rhs.event_time; - } - return lhs.ordinal < rhs.ordinal; - }); - - if (rows.size() > 10) { - rows.resize(10); - } - - Batch result( - Schema{{ColumnSchema("SearchPhrase", ColumnType::String), ColumnSchema("EventTime", ColumnType::Timestamp)}}, - rows.size()); - - for (const auto& row : rows) { - result.AppendValueFromString(0, row.search_phrase); - result.AppendValueFromString(1, TimestampToString(static_cast(row.event_time))); - } - - return result; -} - -inline ExecuteExpected SelectSearchPhraseOrderByPhrase(const std::filesystem::path& path) { - const ColumnarBatchReader reader(path); - const auto scan = ScanColumns(path, reader.GetSchema(), {"SearchPhrase"}); - - StringArena arena; - std::vector rows; - - while (const auto batch = scan->Next()) { - const Column& search_phrase = batch->ColumnAt(0); - for (size_t row = 0; row < batch->RowsCount(); ++row) { - const std::string_view phrase = arena.Store(search_phrase.ValueAsString(row)); - if (phrase.empty()) { - continue; - } - rows.push_back(phrase); - } - } - - std::ranges::sort(rows); - - if (rows.size() > 10) { - rows.resize(10); - } - - Batch result(Schema{{ColumnSchema("SearchPhrase", ColumnType::String)}}, rows.size()); - - for (const std::string_view row : rows) { - result.AppendValueFromString(0, row); - } - - return result; -} - -inline ExecuteExpected SelectSearchPhraseOrderByEventTimeThenPhrase(const std::filesystem::path& path) { - const ColumnarBatchReader reader(path); - const auto scan = ScanColumns(path, reader.GetSchema(), {"EventTime", "SearchPhrase"}); - - StringArena arena; - std::vector rows; - size_t ordinal = 0; - - while (const auto batch = scan->Next()) { - const Column& event_time = batch->ColumnAt(0); - const Column& search_phrase = batch->ColumnAt(1); - for (size_t row = 0; row < batch->RowsCount(); ++row) { - const std::string_view phrase = arena.Store(search_phrase.ValueAsString(row)); - if (phrase.empty()) { - continue; - } - rows.push_back(SearchPhraseOrderRow{ - .event_time = event_time.ValueAsInt128(row), .search_phrase = phrase, .ordinal = ordinal++}); - } - } - - std::ranges::sort(rows, [](const SearchPhraseOrderRow& lhs, const SearchPhraseOrderRow& rhs) { - if (lhs.event_time != rhs.event_time) { - return lhs.event_time < rhs.event_time; - } - if (lhs.search_phrase != rhs.search_phrase) { - return lhs.search_phrase < rhs.search_phrase; - } - return lhs.ordinal < rhs.ordinal; - }); - - if (rows.size() > 10) { - rows.resize(10); - } - - Batch result( - Schema{{ColumnSchema("SearchPhrase", ColumnType::String), ColumnSchema("EventTime", ColumnType::Timestamp)}}, - rows.size()); - - for (const auto& row : rows) { - result.AppendValueFromString(0, row.search_phrase); - result.AppendValueFromString(1, TimestampToString(static_cast(row.event_time))); - } - - return result; -} - -inline std::optional TryRun(const std::filesystem::path& path, const int query_id) { - switch (query_id) { - case 0: - return CountAll(path); - case 1: - return CountAdvEngineNonZero(path); - case 2: - return SumCountAvg(path); - case 3: - return AvgUserId(path); - case 4: - return CountDistinctColumn(path, "UserID", "COUNT(DISTINCT UserID)"); - case 5: - return CountDistinctColumn(path, "SearchPhrase", "COUNT(DISTINCT SearchPhrase)"); - case 6: - return MinMaxEventDate(path); - case 20: - return CountUrlContainsGoogle(path); - case 21: - return SearchPhraseGroupByUrlContainsGoogle(path); - case 22: - return SearchPhraseGroupByTitleGoogle(path); - case 24: - return SelectSearchPhraseOrderByEventTime(path); - case 25: - return SelectSearchPhraseOrderByPhrase(path); - case 26: - return SelectSearchPhraseOrderByEventTimeThenPhrase(path); - case 29: - return SumWidthVariants(path); - case 30: - return GroupBySearchEngineClientIP(path, true); - case 31: - return GroupByWatchClientIP(path, true); - case 32: - return GroupByWatchClientIP(path, false); - case 33: - return GroupByURLCount(path, false); - case 34: - return GroupByURLCount(path, true); - case 35: - return GroupByClientIPDerived(path); - default: - return std::nullopt; - } -} - +std::optional TryRun(const std::filesystem::path& path, int query_id); } // namespace clickbench_fast -inline bool HasHardcodedClickBenchQuery(const int query_id) { return query_id >= 0 && query_id <= 42; } - -inline Query BuildRequiredClickBenchQuery(const int query_id) { - auto query = clickbench::BuildClickBenchQuery(query_id); - if (!query.has_value()) { - throw Error::InvalidArgument("benchmark", "unknown ClickBench query id"); - } - return std::move(*query); -} - -inline ExecuteExpected RunHardcodedClickBenchQuery(const Executor& executor, const int query_id) { - return executor.Execute(BuildRequiredClickBenchQuery(query_id)); -} +bool HasHardcodedClickBenchQuery(int query_id); +Query BuildRequiredClickBenchQuery(int query_id); +ExecuteExpected RunHardcodedClickBenchQuery(const Executor& executor, int query_id); diff --git a/benchmarks/support/query_runners_fast.cpp b/benchmarks/support/query_runners_fast.cpp new file mode 100644 index 0000000..cb5f184 --- /dev/null +++ b/benchmarks/support/query_runners_fast.cpp @@ -0,0 +1,715 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "common/parsing.h" +#include "common/string_arena.h" +#include "executor/operators_internal.h" +#include "executor/query_utils.h" +#include "io/columnar_batch.h" +#include "query_runners.h" + +namespace detail { + +constexpr uint64_t hash_magic = 0x9e3779b97f4a7c15ULL; + +struct Int128Hash { + size_t operator()(const Int128 value) const noexcept { + const UInt128 raw = static_cast(value); + const uint64_t low = static_cast(raw); + const uint64_t high = static_cast(raw >> 64); + return std::hash{}(low ^ (high + hash_magic + (low << 6) + (low >> 2))); + } +}; + +struct Int128PairHash { + size_t operator()(const std::pair& value) const noexcept { + const size_t lhs = Int128Hash{}(value.first); + const size_t rhs = Int128Hash{}(value.second); + return lhs ^ (rhs + hash_magic + (lhs << 6) + (lhs >> 2)); + } +}; + +struct SearchPhraseGroup { + std::string_view key; + uint64_t count = 0; + std::string_view min_url; + size_t ordinal = 0; +}; + +struct SearchPhraseStats { + std::string_view key; + uint64_t count = 0; + std::string_view min_url; + std::string_view min_title; + std::unordered_set distinct_users; + size_t ordinal = 0; +}; + +struct IntGroupStats { + Int128 key1 = 0; + Int128 key2 = 0; + uint64_t count = 0; + Int128 sum_refresh = 0; + Int128 sum_width = 0; + size_t ordinal = 0; +}; + +struct SearchPhraseOrderRow { + Int128 event_time = 0; + std::string_view search_phrase; + size_t ordinal = 0; +}; + +} // namespace detail + +using detail::Int128Hash; +using detail::Int128PairHash; +using detail::IntGroupStats; +using detail::SearchPhraseGroup; +using detail::SearchPhraseOrderRow; +using detail::SearchPhraseStats; + +static Batch SingleRow(const Schema& schema, const std::initializer_list values) { + Batch batch(schema, 1); + size_t column = 0; + + for (const std::string_view value : values) { + batch.AppendValueFromString(column++, value); + } + + return batch; +} + +static std::unique_ptr ScanColumns(const std::filesystem::path& path, const Schema& schema, + const std::initializer_list columns) { + std::vector projection_indexes; + projection_indexes.reserve(columns.size()); + + for (const std::string_view column : columns) { + projection_indexes.push_back(FindColumnIndex(schema, column)); + } + + return CreateScanOperator(path, std::move(projection_indexes), {}); +} + +static bool Contains(std::string_view haystack, std::string_view needle) { + return haystack.find(needle) != std::string_view::npos; +} + +template +static std::vector OrderGroupsByCount(const Map& groups, const size_t limit) { + std::vector ordered; + ordered.reserve(groups.size()); + + for (const auto& entry : groups) { + ordered.push_back(&entry.second); + } + + std::ranges::sort(ordered, [](const auto* lhs, const auto* rhs) { + if (lhs->count != rhs->count) { + return lhs->count > rhs->count; + } + return lhs->ordinal < rhs->ordinal; + }); + + if (ordered.size() > limit) { + ordered.resize(limit); + } + + return ordered; +} + +static ExecuteExpected CountAll(const std::filesystem::path& path) { + const ColumnarBatchReader reader(path); + uint64_t rows = 0; + + for (const auto& row_group : reader.GetMetadata().row_groups) { + rows += row_group.row_count; + } + + return SingleRow(Schema{{ColumnSchema("COUNT(*)", ColumnType::Int64)}}, {std::to_string(rows)}); +} + +static ExecuteExpected CountAdvEngineNonZero(const std::filesystem::path& path) { + const ColumnarBatchReader reader(path); + const auto scan = ScanColumns(path, reader.GetSchema(), {"AdvEngineID"}); + + uint64_t count = 0; + + while (const auto batch = scan->Next()) { + const Column& adv_engine = batch->ColumnAt(0); + for (size_t row = 0; row < batch->RowsCount(); ++row) { + if (adv_engine.ValueAsInt128(row) != 0) { + ++count; + } + } + } + + return SingleRow(Schema{{ColumnSchema("COUNT(*)", ColumnType::Int64)}}, {std::to_string(count)}); +} + +static ExecuteExpected SumCountAvg(const std::filesystem::path& path) { + const ColumnarBatchReader reader(path); + const auto scan = ScanColumns(path, reader.GetSchema(), {"AdvEngineID", "ResolutionWidth"}); + + Int128 adv_sum = 0; + Int128 width_sum = 0; + uint64_t count = 0; + + while (const auto batch = scan->Next()) { + const Column& adv_engine = batch->ColumnAt(0); + const Column& width = batch->ColumnAt(1); + count += batch->RowsCount(); + for (size_t row = 0; row < batch->RowsCount(); ++row) { + adv_sum += adv_engine.ValueAsInt128(row); + width_sum += width.ValueAsInt128(row); + } + } + + return SingleRow( + Schema{{ColumnSchema("SUM(AdvEngineID)", ColumnType::Int128), ColumnSchema("COUNT(*)", ColumnType::Int64), + ColumnSchema("AVG(ResolutionWidth)", ColumnType::Int128)}}, + {Int128ToString(adv_sum), std::to_string(count), + count == 0 ? "0" : Int128ToString(width_sum / static_cast(count))}); +} + +static ExecuteExpected AvgUserId(const std::filesystem::path& path) { + const ColumnarBatchReader reader(path); + const auto scan = ScanColumns(path, reader.GetSchema(), {"UserID"}); + + Int128 sum = 0; + uint64_t count = 0; + + while (const auto batch = scan->Next()) { + const Column& user_id = batch->ColumnAt(0); + count += batch->RowsCount(); + for (size_t row = 0; row < batch->RowsCount(); ++row) { + sum += user_id.ValueAsInt128(row); + } + } + + return SingleRow(Schema{{ColumnSchema("AVG(UserID)", ColumnType::Int128)}}, + {count == 0 ? "0" : Int128ToString(sum / static_cast(count))}); +} + +static ExecuteExpected CountDistinctColumn(const std::filesystem::path& path, const std::string_view column_name, + const std::string_view output_name) { + const ColumnarBatchReader reader(path); + const auto scan = ScanColumns(path, reader.GetSchema(), {column_name}); + + std::unordered_set values; + + while (const auto batch = scan->Next()) { + const Column& column = batch->ColumnAt(0); + for (size_t row = 0; row < batch->RowsCount(); ++row) { + values.insert(column.ValueAsString(row)); + } + } + + return SingleRow(Schema{{ColumnSchema(std::string(output_name), ColumnType::Int64)}}, + {std::to_string(values.size())}); +} + +static ExecuteExpected MinMaxEventDate(const std::filesystem::path& path) { + const ColumnarBatchReader reader(path); + const size_t event_date = FindColumnIndex(reader.GetSchema(), "EventDate"); + + bool has_value = false; + Int128 min_value = 0; + Int128 max_value = 0; + + for (const auto& row_group : reader.GetMetadata().row_groups) { + const auto& chunk = row_group.columns[event_date]; + if (!chunk.has_min_max) { + continue; + } + if (!has_value) { + min_value = chunk.min_value; + max_value = chunk.max_value; + has_value = true; + } else { + min_value = std::min(min_value, chunk.min_value); + max_value = std::max(max_value, chunk.max_value); + } + } + + return SingleRow( + Schema{{ColumnSchema("MIN(EventDate)", ColumnType::Date), ColumnSchema("MAX(EventDate)", ColumnType::Date)}}, + {DateToString(static_cast(min_value)), DateToString(static_cast(max_value))}); +} + +static ExecuteExpected CountUrlContainsGoogle(const std::filesystem::path& path) { + const ColumnarBatchReader reader(path); + const auto scan = ScanColumns(path, reader.GetSchema(), {"URL"}); + + uint64_t count = 0; + + while (const auto batch = scan->Next()) { + const Column& url = batch->ColumnAt(0); + for (size_t row = 0; row < batch->RowsCount(); ++row) { + if (Contains(url.ValueAsString(row), "google")) { + ++count; + } + } + } + + return SingleRow(Schema{{ColumnSchema("COUNT(*)", ColumnType::Int64)}}, {std::to_string(count)}); +} + +static ExecuteExpected SearchPhraseGroupByUrlContainsGoogle(const std::filesystem::path& path) { + const ColumnarBatchReader reader(path); + const auto scan = ScanColumns(path, reader.GetSchema(), {"URL", "SearchPhrase"}); + + StringArena arena; + std::unordered_map groups; + size_t ordinal = 0; + + while (const auto batch = scan->Next()) { + const Column& url = batch->ColumnAt(0); + const Column& search_phrase = batch->ColumnAt(1); + + for (size_t row = 0; row < batch->RowsCount(); ++row) { + if (!Contains(url.ValueAsString(row), "google")) { + continue; + } + + const std::string_view key = arena.Store(search_phrase.ValueAsString(row)); + if (key.empty()) { + continue; + } + + auto& group = groups[key]; + if (group.count == 0) { + group.key = key; + group.ordinal = ordinal++; + group.min_url = arena.Store(url.ValueAsString(row)); + } else if (url.ValueAsString(row) < group.min_url) { + group.min_url = arena.Store(url.ValueAsString(row)); + } + + ++group.count; + } + } + + const auto ordered = OrderGroupsByCount(groups, 10); + Batch result(Schema{{ColumnSchema("SearchPhrase", ColumnType::String), ColumnSchema("MIN(URL)", ColumnType::String), + ColumnSchema("c", ColumnType::Int64)}}, + ordered.size()); + + for (const SearchPhraseGroup* group : ordered) { + result.AppendValueFromString(0, group->key); + result.AppendValueFromString(1, group->min_url); + result.AppendValueFromString(2, std::to_string(group->count)); + } + + return result; +} + +static ExecuteExpected SearchPhraseGroupByTitleGoogle(const std::filesystem::path& path) { + const ColumnarBatchReader reader(path); + const auto scan = ScanColumns(path, reader.GetSchema(), {"Title", "URL", "SearchPhrase", "UserID"}); + + StringArena arena; + std::unordered_map groups; + size_t ordinal = 0; + + while (const auto batch = scan->Next()) { + const Column& title = batch->ColumnAt(0); + const Column& url = batch->ColumnAt(1); + const Column& search_phrase = batch->ColumnAt(2); + const Column& user_id = batch->ColumnAt(3); + + for (size_t row = 0; row < batch->RowsCount(); ++row) { + const std::string_view title_value = arena.Store(title.ValueAsString(row)); + if (!Contains(title_value, "Google")) { + continue; + } + + const std::string_view url_value = arena.Store(url.ValueAsString(row)); + if (Contains(url_value, ".google.")) { + continue; + } + + const std::string_view key = arena.Store(search_phrase.ValueAsString(row)); + if (key.empty()) { + continue; + } + + auto& group = groups[key]; + if (group.count == 0) { + group.key = key; + group.ordinal = ordinal++; + group.min_url = url_value; + group.min_title = title_value; + } else { + if (url_value < group.min_url) { + group.min_url = url_value; + } + if (title_value < group.min_title) { + group.min_title = title_value; + } + } + + group.distinct_users.insert(user_id.ValueAsInt128(row)); + ++group.count; + } + } + + const auto ordered = OrderGroupsByCount(groups, 10); + Batch result(Schema{{ColumnSchema("SearchPhrase", ColumnType::String), ColumnSchema("MIN(URL)", ColumnType::String), + ColumnSchema("MIN(Title)", ColumnType::String), ColumnSchema("c", ColumnType::Int64), + ColumnSchema("COUNT(DISTINCT UserID)", ColumnType::Int64)}}, + ordered.size()); + + for (const SearchPhraseStats* group : ordered) { + result.AppendValueFromString(0, group->key); + result.AppendValueFromString(1, group->min_url); + result.AppendValueFromString(2, group->min_title); + result.AppendValueFromString(3, std::to_string(group->count)); + result.AppendValueFromString(4, std::to_string(group->distinct_users.size())); + } + + return result; +} + +static ExecuteExpected SumWidthVariants(const std::filesystem::path& path) { + const ColumnarBatchReader reader(path); + const auto scan = ScanColumns(path, reader.GetSchema(), {"ResolutionWidth"}); + + Int128 width_sum = 0; + uint64_t row_count = 0; + + while (const auto batch = scan->Next()) { + const Column& width = batch->ColumnAt(0); + row_count += batch->RowsCount(); + for (size_t row = 0; row < batch->RowsCount(); ++row) { + width_sum += width.ValueAsInt128(row); + } + } + + std::vector schema_columns; + std::vector values; + + schema_columns.emplace_back("SUM(ResolutionWidth)", ColumnType::Int128); + values.push_back(Int128ToString(width_sum)); + + for (int offset = 1; offset <= 89; ++offset) { + schema_columns.emplace_back("SUM(ResolutionWidth + " + std::to_string(offset) + ")", ColumnType::Int128); + values.push_back(Int128ToString(width_sum + static_cast(offset) * static_cast(row_count))); + } + + Batch result(Schema{schema_columns}, 1); + for (size_t i = 0; i < values.size(); ++i) { + result.AppendValueFromString(i, values[i]); + } + + return result; +} + +static ExecuteExpected GroupByIntPairWithMetrics(const std::filesystem::path& path, const std::string_view key1_name, + const std::string_view key2_name, const bool filter_search_phrase) { + const ColumnarBatchReader reader(path); + const auto scan = + ScanColumns(path, reader.GetSchema(), {key1_name, key2_name, "IsRefresh", "ResolutionWidth", "SearchPhrase"}); + + std::unordered_map, IntGroupStats, Int128PairHash> groups; + size_t ordinal = 0; + + while (const auto batch = scan->Next()) { + const Column& key1 = batch->ColumnAt(0); + const Column& key2 = batch->ColumnAt(1); + const Column& is_refresh = batch->ColumnAt(2); + const Column& width = batch->ColumnAt(3); + const Column& search_phrase = batch->ColumnAt(4); + + for (size_t row = 0; row < batch->RowsCount(); ++row) { + if (filter_search_phrase && search_phrase.ValueAsString(row).empty()) { + continue; + } + + const std::pair key{key1.ValueAsInt128(row), key2.ValueAsInt128(row)}; + + auto& group = groups[key]; + if (group.count == 0) { + group.key1 = key.first; + group.key2 = key.second; + group.ordinal = ordinal++; + } + + ++group.count; + group.sum_refresh += is_refresh.ValueAsInt128(row); + group.sum_width += width.ValueAsInt128(row); + } + } + + const auto ordered = OrderGroupsByCount(groups, 10); + Batch result(Schema{{ColumnSchema(std::string(key1_name), ColumnType::Int128), + ColumnSchema(std::string(key2_name), ColumnType::Int128), ColumnSchema("c", ColumnType::Int64), + ColumnSchema("SUM(IsRefresh)", ColumnType::Int128), + ColumnSchema("AVG(ResolutionWidth)", ColumnType::Int128)}}, + ordered.size()); + + for (const IntGroupStats* group : ordered) { + result.AppendValueFromString(0, Int128ToString(group->key1)); + result.AppendValueFromString(1, Int128ToString(group->key2)); + result.AppendValueFromString(2, std::to_string(group->count)); + result.AppendValueFromString(3, Int128ToString(group->sum_refresh)); + result.AppendValueFromString( + 4, group->count == 0 ? "0" : Int128ToString(group->sum_width / static_cast(group->count))); + } + + return result; +} + +static ExecuteExpected GroupByURLCount(const std::filesystem::path& path, const bool add_constant_column) { + const ColumnarBatchReader reader(path); + const auto scan = ScanColumns(path, reader.GetSchema(), {"URL"}); + + StringArena arena; + std::unordered_map groups; + size_t ordinal = 0; + + while (const auto batch = scan->Next()) { + const Column& url = batch->ColumnAt(0); + for (size_t row = 0; row < batch->RowsCount(); ++row) { + const std::string_view key = arena.Store(url.ValueAsString(row)); + auto& group = groups[key]; + if (group.count == 0) { + group.key = key; + group.ordinal = ordinal++; + } + ++group.count; + } + } + + const auto ordered = OrderGroupsByCount(groups, 10); + Batch result(add_constant_column + ? Schema{{ColumnSchema("1", ColumnType::Int64), ColumnSchema("URL", ColumnType::String), + ColumnSchema("COUNT(*)", ColumnType::Int64)}} + : Schema{{ColumnSchema("URL", ColumnType::String), ColumnSchema("COUNT(*)", ColumnType::Int64)}}, + ordered.size()); + + for (const SearchPhraseStats* group : ordered) { + if (add_constant_column) { + result.AppendValueFromString(0, "1"); + result.AppendValueFromString(1, group->key); + result.AppendValueFromString(2, std::to_string(group->count)); + } else { + result.AppendValueFromString(0, group->key); + result.AppendValueFromString(1, std::to_string(group->count)); + } + } + + return result; +} + +static ExecuteExpected GroupByClientIPDerived(const std::filesystem::path& path) { + const ColumnarBatchReader reader(path); + const auto scan = ScanColumns(path, reader.GetSchema(), {"ClientIP"}); + + std::unordered_map groups; + size_t ordinal = 0; + + while (const auto batch = scan->Next()) { + const Column& client_ip = batch->ColumnAt(0); + for (size_t row = 0; row < batch->RowsCount(); ++row) { + const Int128 key = client_ip.ValueAsInt128(row); + auto& group = groups[key]; + if (group.count == 0) { + group.key1 = key; + group.ordinal = ordinal++; + } + ++group.count; + } + } + + const auto ordered = OrderGroupsByCount(groups, 10); + Batch result( + Schema{{ColumnSchema("ClientIP", ColumnType::Int128), ColumnSchema("ClientIP - 1", ColumnType::Int128), + ColumnSchema("ClientIP - 2", ColumnType::Int128), ColumnSchema("ClientIP - 3", ColumnType::Int128), + ColumnSchema("COUNT(*)", ColumnType::Int64)}}, + ordered.size()); + + for (const IntGroupStats* group : ordered) { + const Int128 key = group->key1; + result.AppendValueFromString(0, Int128ToString(key)); + result.AppendValueFromString(1, Int128ToString(key - 1)); + result.AppendValueFromString(2, Int128ToString(key - 2)); + result.AppendValueFromString(3, Int128ToString(key - 3)); + result.AppendValueFromString(4, std::to_string(group->count)); + } + + return result; +} + +static std::vector CollectSearchPhraseOrderRows(const std::filesystem::path& path) { + const ColumnarBatchReader reader(path); + const auto scan = ScanColumns(path, reader.GetSchema(), {"EventTime", "SearchPhrase"}); + + StringArena arena; + std::vector rows; + size_t ordinal = 0; + + while (const auto batch = scan->Next()) { + const Column& event_time = batch->ColumnAt(0); + const Column& search_phrase = batch->ColumnAt(1); + + for (size_t row = 0; row < batch->RowsCount(); ++row) { + const std::string_view phrase = arena.Store(search_phrase.ValueAsString(row)); + if (phrase.empty()) { + continue; + } + rows.push_back(SearchPhraseOrderRow{ + .event_time = event_time.ValueAsInt128(row), .search_phrase = phrase, .ordinal = ordinal++}); + } + } + + return rows; +} + +static ExecuteExpected SelectSearchPhraseOrderByEventTime(const std::filesystem::path& path) { + auto rows = CollectSearchPhraseOrderRows(path); + + std::ranges::sort(rows, [](const SearchPhraseOrderRow& lhs, const SearchPhraseOrderRow& rhs) { + if (lhs.event_time != rhs.event_time) { + return lhs.event_time < rhs.event_time; + } + return lhs.ordinal < rhs.ordinal; + }); + + if (rows.size() > 10) { + rows.resize(10); + } + + Batch result( + Schema{{ColumnSchema("SearchPhrase", ColumnType::String), ColumnSchema("EventTime", ColumnType::Timestamp)}}, + rows.size()); + + for (const auto& row : rows) { + result.AppendValueFromString(0, row.search_phrase); + result.AppendValueFromString(1, TimestampToString(static_cast(row.event_time))); + } + + return result; +} + +static ExecuteExpected SelectSearchPhraseOrderByPhrase(const std::filesystem::path& path) { + const ColumnarBatchReader reader(path); + const auto scan = ScanColumns(path, reader.GetSchema(), {"SearchPhrase"}); + + StringArena arena; + std::vector rows; + + while (const auto batch = scan->Next()) { + const Column& search_phrase = batch->ColumnAt(0); + for (size_t row = 0; row < batch->RowsCount(); ++row) { + const std::string_view phrase = arena.Store(search_phrase.ValueAsString(row)); + if (phrase.empty()) { + continue; + } + rows.push_back(phrase); + } + } + + std::ranges::sort(rows); + + if (rows.size() > 10) { + rows.resize(10); + } + + Batch result(Schema{{ColumnSchema("SearchPhrase", ColumnType::String)}}, rows.size()); + for (const std::string_view row : rows) { + result.AppendValueFromString(0, row); + } + + return result; +} + +static ExecuteExpected SelectSearchPhraseOrderByEventTimeThenPhrase(const std::filesystem::path& path) { + auto rows = CollectSearchPhraseOrderRows(path); + + std::ranges::sort(rows, [](const SearchPhraseOrderRow& lhs, const SearchPhraseOrderRow& rhs) { + if (lhs.event_time != rhs.event_time) { + return lhs.event_time < rhs.event_time; + } + if (lhs.search_phrase != rhs.search_phrase) { + return lhs.search_phrase < rhs.search_phrase; + } + return lhs.ordinal < rhs.ordinal; + }); + + if (rows.size() > 10) { + rows.resize(10); + } + + Batch result( + Schema{{ColumnSchema("SearchPhrase", ColumnType::String), ColumnSchema("EventTime", ColumnType::Timestamp)}}, + rows.size()); + + for (const auto& row : rows) { + result.AppendValueFromString(0, row.search_phrase); + result.AppendValueFromString(1, TimestampToString(static_cast(row.event_time))); + } + + return result; +} + +namespace clickbench_fast { + +std::optional TryRun(const std::filesystem::path& path, const int query_id) { + switch (query_id) { + case 0: + return CountAll(path); + case 1: + return CountAdvEngineNonZero(path); + case 2: + return SumCountAvg(path); + case 3: + return AvgUserId(path); + case 4: + return CountDistinctColumn(path, "UserID", "COUNT(DISTINCT UserID)"); + case 5: + return CountDistinctColumn(path, "SearchPhrase", "COUNT(DISTINCT SearchPhrase)"); + case 6: + return MinMaxEventDate(path); + case 20: + return CountUrlContainsGoogle(path); + case 21: + return SearchPhraseGroupByUrlContainsGoogle(path); + case 22: + return SearchPhraseGroupByTitleGoogle(path); + case 24: + return SelectSearchPhraseOrderByEventTime(path); + case 25: + return SelectSearchPhraseOrderByPhrase(path); + case 26: + return SelectSearchPhraseOrderByEventTimeThenPhrase(path); + case 29: + return SumWidthVariants(path); + case 30: + return GroupByIntPairWithMetrics(path, "SearchEngineID", "ClientIP", true); + case 31: + return GroupByIntPairWithMetrics(path, "WatchID", "ClientIP", true); + case 32: + return GroupByIntPairWithMetrics(path, "WatchID", "ClientIP", false); + case 33: + return GroupByURLCount(path, false); + case 34: + return GroupByURLCount(path, true); + case 35: + return GroupByClientIPDerived(path); + default: + return std::nullopt; + } +} + +} // namespace clickbench_fast From 6e7ef0315bfa5af6e2a891c323345edd17d7047a Mon Sep 17 00:00:00 2001 From: ik4rd Date: Sun, 31 May 2026 16:56:40 +0300 Subject: [PATCH 09/16] fix: refactoring --- benchmarks/support/query_runners_fast.cpp | 16 +++++++++++----- include/executor/aggregate_function.h | 1 + include/executor/executor.h | 1 + include/io/file.h | 5 ++++- include/model/batch.h | 1 + include/sql_parser/tokenizer.h | 2 ++ src/executor/aggregate_states.cpp | 4 ++++ src/executor/operators_aggregate.cpp | 3 +++ src/executor/operators_runtime.cpp | 3 +++ 9 files changed, 30 insertions(+), 6 deletions(-) diff --git a/benchmarks/support/query_runners_fast.cpp b/benchmarks/support/query_runners_fast.cpp index cb5f184..9417cfd 100644 --- a/benchmarks/support/query_runners_fast.cpp +++ b/benchmarks/support/query_runners_fast.cpp @@ -26,7 +26,7 @@ struct Int128Hash { const UInt128 raw = static_cast(value); const uint64_t low = static_cast(raw); const uint64_t high = static_cast(raw >> 64); - return std::hash{}(low ^ (high + hash_magic + (low << 6) + (low >> 2))); + return std::hash{}(low ^ high + hash_magic + (low << 6) + (low >> 2)); } }; @@ -34,38 +34,44 @@ struct Int128PairHash { size_t operator()(const std::pair& value) const noexcept { const size_t lhs = Int128Hash{}(value.first); const size_t rhs = Int128Hash{}(value.second); - return lhs ^ (rhs + hash_magic + (lhs << 6) + (lhs >> 2)); + return lhs ^ rhs + hash_magic + (lhs << 6) + (lhs >> 2); } }; struct SearchPhraseGroup { std::string_view key; - uint64_t count = 0; std::string_view min_url; + + uint64_t count = 0; size_t ordinal = 0; }; struct SearchPhraseStats { std::string_view key; - uint64_t count = 0; std::string_view min_url; std::string_view min_title; + std::unordered_set distinct_users; + + uint64_t count = 0; size_t ordinal = 0; }; struct IntGroupStats { Int128 key1 = 0; Int128 key2 = 0; + uint64_t count = 0; Int128 sum_refresh = 0; Int128 sum_width = 0; + size_t ordinal = 0; }; struct SearchPhraseOrderRow { Int128 event_time = 0; std::string_view search_phrase; + size_t ordinal = 0; }; @@ -101,7 +107,7 @@ static std::unique_ptr ScanColumns(const std::filesystem::path& path, return CreateScanOperator(path, std::move(projection_indexes), {}); } -static bool Contains(std::string_view haystack, std::string_view needle) { +static bool Contains(const std::string_view haystack, const std::string_view needle) { return haystack.find(needle) != std::string_view::npos; } diff --git a/include/executor/aggregate_function.h b/include/executor/aggregate_function.h index aeecdad..7d90a8e 100644 --- a/include/executor/aggregate_function.h +++ b/include/executor/aggregate_function.h @@ -30,6 +30,7 @@ constexpr bool HasAggCallFeature(const AggCallFeature features, const AggCallFea struct AggFuncDefinition { std::string_view canonical_name; + AggCallFeature call_features = AggCallFeature::None; AggTypeSupport supports_type = nullptr; AggFactory factory = nullptr; diff --git a/include/executor/executor.h b/include/executor/executor.h index 0548ef3..ccfdd55 100644 --- a/include/executor/executor.h +++ b/include/executor/executor.h @@ -31,5 +31,6 @@ class Executor { static ExecuteExpected ExecutePlanned(const PlannedQuery& planned); std::unordered_map tables_; + bool unsupported_fallback_enabled_ = false; }; diff --git a/include/io/file.h b/include/io/file.h index 7ab909d..9c4f70a 100644 --- a/include/io/file.h +++ b/include/io/file.h @@ -10,9 +10,11 @@ struct FileMetadata { uintmax_t size = 0; + + std::filesystem::file_time_type last_write_time; + bool is_regular = false; bool is_directory = false; - std::filesystem::file_time_type last_write_time; }; std::optional GetFileMetadata(const std::filesystem::path& path); @@ -45,6 +47,7 @@ class InputFile { private: std::filesystem::path path_; + std::ifstream in_; }; diff --git a/include/model/batch.h b/include/model/batch.h index 43d6dea..606dfae 100644 --- a/include/model/batch.h +++ b/include/model/batch.h @@ -42,5 +42,6 @@ class Batch { private: Schema schema_; + std::vector> columns_; }; diff --git a/include/sql_parser/tokenizer.h b/include/sql_parser/tokenizer.h index 3743bce..f8ff441 100644 --- a/include/sql_parser/tokenizer.h +++ b/include/sql_parser/tokenizer.h @@ -78,6 +78,7 @@ class Token { private: std::string text_; + size_t offset_ = 0; }; @@ -161,6 +162,7 @@ class Tokenizer { private: std::string query_; + size_t pos_ = 0; }; diff --git a/src/executor/aggregate_states.cpp b/src/executor/aggregate_states.cpp index 6083cb3..d4cd162 100644 --- a/src/executor/aggregate_states.cpp +++ b/src/executor/aggregate_states.cpp @@ -64,6 +64,7 @@ class SumAS final : public AggState { private: ColumnType type_; + Int128 sum_ = 0; }; @@ -87,6 +88,7 @@ class AvgAS final : public AggState { private: ColumnType type_; + Int128 sum_ = 0; uint64_t count_ = 0; }; @@ -124,6 +126,7 @@ class ExtremumAS final : public AggState { private: ColumnType type_; bool is_min_ = true; + std::optional extremum_; std::optional typed_extremum_; }; @@ -144,6 +147,7 @@ class DistinctAS final : public AggState { private: std::unique_ptr nested_; + StringArena arena_; StringViewSet seen_; }; diff --git a/src/executor/operators_aggregate.cpp b/src/executor/operators_aggregate.cpp index 4db07cc..672e5c0 100644 --- a/src/executor/operators_aggregate.cpp +++ b/src/executor/operators_aggregate.cpp @@ -538,7 +538,9 @@ class AggOperator final : public Operator { private: std::unique_ptr child_; PredicatePtr filter_; + std::vector bindings_; + bool returned_ = false; }; @@ -612,6 +614,7 @@ class GroupAggOperator final : public Operator { std::vector states; mutable std::unique_ptr finalized_aggregates; + size_t ordinal = 0; }; diff --git a/src/executor/operators_runtime.cpp b/src/executor/operators_runtime.cpp index 2e35cbc..7f74691 100644 --- a/src/executor/operators_runtime.cpp +++ b/src/executor/operators_runtime.cpp @@ -288,6 +288,7 @@ class RowOrdering { private: std::vector order_by_; + const std::vector* batches_ = nullptr; }; @@ -570,6 +571,7 @@ class LimitOperator final : public Operator { private: std::unique_ptr child_; + size_t remaining_; }; @@ -597,6 +599,7 @@ class OffsetOperator final : public Operator { private: std::unique_ptr child_; + size_t remaining_; }; From 8671fa11a7e90ef428368c0f68d8d0d90892e69c Mon Sep 17 00:00:00 2001 From: ik4rd Date: Sun, 31 May 2026 16:57:09 +0300 Subject: [PATCH 10/16] fix: row group size issue --- script/convert.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/script/convert.sh b/script/convert.sh index c64cc61..b1498c3 100755 --- a/script/convert.sh +++ b/script/convert.sh @@ -11,7 +11,7 @@ columnar_path="$2" script_dir="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" repo_dir="$(cd "${script_dir}/.." && pwd)" build_dir="${BUILD_DIR:-build}" -row_group_size="${ROW_GROUP_SIZE:-50000}" +row_group_size="${ROW_GROUP_SIZE:-16384}" compression="${COLUMNAR_COMPRESSION:-lz4}" schema_path="${COLUMNAR_SCHEMA:-${repo_dir}/benchmarks/scheme.csv}" binary="${repo_dir}/${build_dir}/columnar_engine" From d5b7e62d68d8738d2976fcec5db87537594b566e Mon Sep 17 00:00:00 2001 From: ik4rd Date: Sun, 31 May 2026 17:04:35 +0300 Subject: [PATCH 11/16] fix: benchmark csv to columnar fix --- benchmarks/benchmark_csv_to_columnar.cpp | 4 +++- benchmarks/scheme.csv | 8 ++++---- 2 files changed, 7 insertions(+), 5 deletions(-) diff --git a/benchmarks/benchmark_csv_to_columnar.cpp b/benchmarks/benchmark_csv_to_columnar.cpp index bbae38d..d8e8d82 100644 --- a/benchmarks/benchmark_csv_to_columnar.cpp +++ b/benchmarks/benchmark_csv_to_columnar.cpp @@ -29,6 +29,7 @@ #endif static constexpr size_t RowsPerGroup = 1 << 14; +static constexpr auto DefaultCompression = Compression::Lz4; struct CsvCompareResult { bool equal = true; @@ -76,7 +77,7 @@ int main(const int argc, char** argv) { const std::filesystem::path roundtrip_data_path = COLUMNAR_BENCHMARK_DEFAULT_ROUNDTRIP_DATA; const auto csv_to_columnar_started_at = std::chrono::steady_clock::now(); - ConvertCsvToColumnar(schema_path, data_path, output_path, rows_per_group); + ConvertCsvToColumnar(schema_path, data_path, output_path, rows_per_group, DefaultCompression); const auto csv_to_columnar_finished_at = std::chrono::steady_clock::now(); const auto columnar_to_csv_started_at = std::chrono::steady_clock::now(); @@ -97,6 +98,7 @@ int main(const int argc, char** argv) { std::cout << "schema: " << schema_path << std::endl << "data: " << data_path << std::endl << "columnar_output: " << output_path << std::endl + << "compression: " << CompressionName(DefaultCompression) << std::endl << "roundtrip_schema: " << roundtrip_schema_path << std::endl << "roundtrip_data: " << roundtrip_data_path << std::endl << "rows_per_group: " << rows_per_group << std::endl diff --git a/benchmarks/scheme.csv b/benchmarks/scheme.csv index 881bb54..a3b7862 100644 --- a/benchmarks/scheme.csv +++ b/benchmarks/scheme.csv @@ -2,8 +2,8 @@ WatchID,int64 JavaEnable,int16 Title,string GoodEvent,int16 -EventTime,TIMESTAMP -EventDate,DATE +EventTime,timestamp +EventDate,date CounterID,int32 ClientIP,int32 RegionID,int32 @@ -43,7 +43,7 @@ IsArtifical,int16 WindowClientWidth,int16 WindowClientHeight,int16 ClientTimeZone,int16 -ClientEventTime,TIMESTAMP +ClientEventTime,timestamp SilverlightVersion1,int16 SilverlightVersion2,int16 SilverlightVersion3,int32 @@ -62,7 +62,7 @@ IsParameter,int16 DontCountHits,int16 WithHash,int16 HitColor,string -LocalEventTime,TIMESTAMP +LocalEventTime,timestamp Age,int16 Sex,int16 Income,int16 From 2a9b55bf948c3bd4aced7468974e80e0e2a61940 Mon Sep 17 00:00:00 2001 From: ik4rd Date: Sun, 31 May 2026 17:20:16 +0300 Subject: [PATCH 12/16] upd: update dashboard generation --- scripts/dashboard.py | 56 +++++++++++++++++++++++++++----------------- 1 file changed, 35 insertions(+), 21 deletions(-) diff --git a/scripts/dashboard.py b/scripts/dashboard.py index 68ccc64..94efe57 100644 --- a/scripts/dashboard.py +++ b/scripts/dashboard.py @@ -111,7 +111,7 @@ def parse_args() -> argparse.Namespace: choices=("none", "once", "per-query"), help=( "Drop the filesystem page cache before benchmarks: never, once before all queries, " - "or before the first run of each query. Defaults to per-query, except --lz4-hardcoded defaults to none." + "or before the first run of each query. Defaults to per-query." ), ) parser.add_argument( @@ -418,7 +418,8 @@ def run_query( def run_hardcoded_benchmark( benchmark_binary: pathlib.Path, input_path: pathlib.Path, - count: int, + query_from: int, + query_to: int, ) -> dict[int, dict[str, str]]: command = [ str(benchmark_binary), @@ -427,9 +428,9 @@ def run_hardcoded_benchmark( "--input", str(input_path), "--from", - "0", + str(query_from), "--to", - str(count - 1), + str(query_to), ] completed = subprocess.run(command, capture_output=True, text=True) @@ -602,37 +603,50 @@ def collect_hardcoded_rows( if cache_drop_mode == "once": drop_file_system_cache(cache_drop_command) - total_runs = warm_runs + 1 - for run_index in range(total_runs): + for query_id, row in rows_by_query.items(): if cache_drop_mode == "per-query": drop_file_system_cache(cache_drop_command) try: - run_results = run_hardcoded_benchmark(benchmark_binary, input_path, count) + first_run_result = run_hardcoded_benchmark(benchmark_binary, input_path, query_id, query_id) except RuntimeError as error: - for row in rows_by_query.values(): + row["status"] = "failed" + row["error"] = str(error) + continue + + result = first_run_result.get(query_id) + if result is None: + row["status"] = "failed" + row["error"] = "missing benchmark result" + continue + if result["status"] != "passed": + row["status"] = "failed" + row["error"] = f"benchmark status={result['status']}" + continue + + row["first_run_ms"] = format_ms(float(result["time_ms"])) + + warm_values = row["warm_values"] + assert isinstance(warm_values, list) + for _ in range(warm_runs): + try: + warm_run_result = run_hardcoded_benchmark(benchmark_binary, input_path, query_id, query_id) + except RuntimeError as error: row["status"] = "failed" row["error"] = str(error) - break + break - for query_id, row in rows_by_query.items(): - result = run_results.get(query_id) + result = warm_run_result.get(query_id) if result is None: row["status"] = "failed" row["error"] = "missing benchmark result" - continue - + break if result["status"] != "passed": row["status"] = "failed" row["error"] = f"benchmark status={result['status']}" + break - elapsed_ms = float(result["time_ms"]) - if run_index == 0: - row["first_run_ms"] = format_ms(elapsed_ms) - else: - warm_values = row["warm_values"] - assert isinstance(warm_values, list) - warm_values.append(elapsed_ms) + warm_values.append(float(result["time_ms"])) rows: list[dict[str, str]] = [] for row in rows_by_query.values(): @@ -882,7 +896,7 @@ def main() -> int: args.query_mode = "hardcoded" args.compression = "lz4" if args.cache_drop_mode is None: - args.cache_drop_mode = "none" if args.lz4_hardcoded else "per-query" + args.cache_drop_mode = "per-query" root = repo_root() binary = resolve_path(args.binary, root) From 87df7947a04e7b71b9af85725b0304a9214790e7 Mon Sep 17 00:00:00 2001 From: ik4rd Date: Sun, 31 May 2026 17:20:39 +0300 Subject: [PATCH 13/16] upd: update dashboard --- README.md | 116 +++++++++++------------ benchmarks/results/readme_benchmarks.csv | 82 ++++++++-------- 2 files changed, 99 insertions(+), 99 deletions(-) diff --git a/README.md b/README.md index cc7c856..8875e93 100644 --- a/README.md +++ b/README.md @@ -14,7 +14,7 @@ ## Benchmark Dashboard -`generated 2026-05-31 15:54:25` · `queries 43/43` · `warm runs 4` · `cache drop none` · `query mode hardcoded` · [`csv`](benchmarks/results/readme_benchmarks.csv) +`generated 2026-05-31 17:18:21` · `queries 43/43` · `warm runs 4` · `cache drop per-query` · `query mode hardcoded` · [`csv`](benchmarks/results/readme_benchmarks.csv) @@ -25,14 +25,14 @@ - - - - + + + + - +
queries ok43 / 43
queries failed0
median warm time152.25 ms
average warm time194.25 ms
p95 warm time518.62 ms
cold/warm delta-8.4%
median warm time149.50 ms
average warm time190.25 ms
p95 warm time521.35 ms
cold/warm delta17.8%
total output size0 B
max output size0 B
fastest queryQ00 · <1 ms
slowest queryQ28 · 919.50 ms
slowest queryQ28 · 940.00 ms
@@ -49,10 +49,10 @@ roundtrip csv size743.0 MB compression ratio6.71x columnar / csv14.9% -csv -> columnar5.69s -columnar -> csv9.57s -convert throughput141.0 MB/s -roundtrip throughput12.5 MB/s +csv -> columnar5.71s +columnar -> csv9.46s +convert throughput140.5 MB/s +roundtrip throughput12.6 MB/s @@ -65,13 +65,13 @@ | slot 1 | slot 2 | slot 3 | slot 4 | slot 5 | slot 6 | | ---: | ---: | ---: | ---: | ---: | ---: | -| Q00 ⬜ `<1` | Q01 🟩 `4.00` | Q02 🟩 `8.50` | Q03 🟩 `6.00` | Q04 🟩 `34.50` | Q05 🟩 `37.50` | -| Q06 ⬜ `<1` | Q07 🟩 `7.00` | Q08 🟥 `191.00` | Q09 🟥 `214.00` | Q10 🟥 `272.00` | Q11 🟥 `267.50` | -| Q12 🟥 `225.50` | Q13 🟥 `259.00` | Q14 🟥 `227.00` | Q15 🟩 `99.00` | Q16 🟦 `149.50` | Q17 🟦 `162.50` | -| Q18 🟥 `510.00` | Q19 ⬜ `<1` | Q20 🟦 `137.50` | Q21 🟦 `155.00` | Q22 🟥 `286.00` | Q23 🟥 `245.00` | -| Q24 🟩 `39.00` | Q25 🟩 `35.00` | Q26 🟩 `38.00` | Q27 🟥 `453.00` | Q28 🟥 `919.50` | Q29 🟩 `7.50` | -| Q30 🟩 `38.00` | Q31 🟩 `56.00` | Q32 🟥 `682.50` | Q33 🟥 `347.50` | Q34 🟥 `333.50` | Q35 🟩 `20.50` | -| Q36 🟥 `369.00` | Q37 🟥 `240.00` | Q38 🟩 `70.50` | Q39 🟥 `461.00` | Q40 🟩 `64.50` | Q41 🟩 `32.50` | +| Q00 ⬜ `<1` | Q01 🟩 `4.00` | Q02 🟩 `7.00` | Q03 🟩 `6.00` | Q04 🟩 `34.50` | Q05 🟩 `36.50` | +| Q06 ⬜ `<1` | Q07 🟩 `8.00` | Q08 🟥 `189.50` | Q09 🟥 `205.00` | Q10 🟥 `260.50` | Q11 🟥 `271.00` | +| Q12 🟥 `223.00` | Q13 🟥 `252.50` | Q14 🟥 `226.00` | Q15 🟩 `89.50` | Q16 🟦 `143.00` | Q17 🟦 `157.00` | +| Q18 🟥 `516.00` | Q19 ⬜ `<1` | Q20 🟦 `136.50` | Q21 🟦 `156.00` | Q22 🟥 `280.00` | Q23 🟥 `239.00` | +| Q24 🟩 `39.00` | Q25 🟩 `36.50` | Q26 🟩 `39.50` | Q27 🟥 `450.50` | Q28 🟥 `940.00` | Q29 🟩 `5.00` | +| Q30 🟩 `37.50` | Q31 🟩 `52.50` | Q32 🟥 `623.00` | Q33 🟥 `322.00` | Q34 🟥 `323.00` | Q35 🟩 `20.00` | +| Q36 🟥 `358.50` | Q37 🟥 `242.50` | Q38 🟩 `70.50` | Q39 🟥 `462.00` | Q40 🟩 `53.00` | Q41 🟩 `30.00` | | Q42 🟩 `64.50` | | | | | | ### Query Table @@ -79,46 +79,46 @@ | Query | Output CSV | First run, ms | Warm avg, ms | Warm median, ms | Warm min, ms | Warm max, ms | Status | | --- | ---: | ---: | ---: | ---: | ---: | ---: | --- | | [Q00](benchmarks/queries/query_0.sql) | 0 B | <1 | <1 | <1 | <1 | <1 | ok | -| [Q01](benchmarks/queries/query_1.sql) | 0 B | 9.00 | 4.25 | 4.00 | 4.00 | 5.00 | ok | -| [Q02](benchmarks/queries/query_2.sql) | 0 B | 11.00 | 8.50 | 8.50 | 7.00 | 10.00 | ok | -| [Q03](benchmarks/queries/query_3.sql) | 0 B | 12.00 | 6.00 | 6.00 | 6.00 | 6.00 | ok | -| [Q04](benchmarks/queries/query_4.sql) | 0 B | 33.00 | 34.75 | 34.50 | 34.00 | 36.00 | ok | -| [Q05](benchmarks/queries/query_5.sql) | 0 B | 44.00 | 37.50 | 37.50 | 36.00 | 39.00 | ok | +| [Q01](benchmarks/queries/query_1.sql) | 0 B | 16.00 | 4.00 | 4.00 | 4.00 | 4.00 | ok | +| [Q02](benchmarks/queries/query_2.sql) | 0 B | 31.00 | 7.00 | 7.00 | 7.00 | 7.00 | ok | +| [Q03](benchmarks/queries/query_3.sql) | 0 B | 24.00 | 6.00 | 6.00 | 6.00 | 6.00 | ok | +| [Q04](benchmarks/queries/query_4.sql) | 0 B | 52.00 | 34.50 | 34.50 | 34.00 | 35.00 | ok | +| [Q05](benchmarks/queries/query_5.sql) | 0 B | 65.00 | 36.75 | 36.50 | 36.00 | 38.00 | ok | | [Q06](benchmarks/queries/query_6.sql) | 0 B | <1 | <1 | <1 | <1 | <1 | ok | -| [Q07](benchmarks/queries/query_7.sql) | 0 B | 8.00 | 7.00 | 7.00 | 7.00 | 7.00 | ok | -| [Q08](benchmarks/queries/query_8.sql) | 0 B | 189.00 | 190.75 | 191.00 | 188.00 | 193.00 | ok | -| [Q09](benchmarks/queries/query_9.sql) | 0 B | 202.00 | 225.00 | 214.00 | 209.00 | 263.00 | ok | -| [Q10](benchmarks/queries/query_10.sql) | 0 B | 267.00 | 272.50 | 272.00 | 268.00 | 278.00 | ok | -| [Q11](benchmarks/queries/query_11.sql) | 0 B | 264.00 | 268.50 | 267.50 | 266.00 | 273.00 | ok | -| [Q12](benchmarks/queries/query_12.sql) | 0 B | 222.00 | 226.25 | 225.50 | 223.00 | 231.00 | ok | -| [Q13](benchmarks/queries/query_13.sql) | 0 B | 253.00 | 258.25 | 259.00 | 252.00 | 263.00 | ok | -| [Q14](benchmarks/queries/query_14.sql) | 0 B | 222.00 | 226.50 | 227.00 | 222.00 | 230.00 | ok | -| [Q15](benchmarks/queries/query_15.sql) | 0 B | 90.00 | 98.75 | 99.00 | 91.00 | 106.00 | ok | -| [Q16](benchmarks/queries/query_16.sql) | 0 B | 140.00 | 151.50 | 149.50 | 144.00 | 163.00 | ok | -| [Q17](benchmarks/queries/query_17.sql) | 0 B | 151.00 | 162.75 | 162.50 | 158.00 | 168.00 | ok | -| [Q18](benchmarks/queries/query_18.sql) | 0 B | 512.00 | 511.25 | 510.00 | 504.00 | 521.00 | ok | -| [Q19](benchmarks/queries/query_19.sql) | 0 B | <1 | 0.25 | <1 | <1 | 1.00 | ok | -| [Q20](benchmarks/queries/query_20.sql) | 0 B | 156.00 | 137.25 | 137.50 | 136.00 | 138.00 | ok | -| [Q21](benchmarks/queries/query_21.sql) | 0 B | 152.00 | 155.50 | 155.00 | 154.00 | 158.00 | ok | -| [Q22](benchmarks/queries/query_22.sql) | 0 B | 312.00 | 285.50 | 286.00 | 282.00 | 288.00 | ok | -| [Q23](benchmarks/queries/query_23.sql) | 0 B | 258.00 | 245.00 | 245.00 | 238.00 | 252.00 | ok | -| [Q24](benchmarks/queries/query_24.sql) | 0 B | 36.00 | 38.25 | 39.00 | 36.00 | 39.00 | ok | -| [Q25](benchmarks/queries/query_25.sql) | 0 B | 35.00 | 36.25 | 35.00 | 35.00 | 40.00 | ok | -| [Q26](benchmarks/queries/query_26.sql) | 0 B | 37.00 | 41.25 | 38.00 | 37.00 | 52.00 | ok | -| [Q27](benchmarks/queries/query_27.sql) | 0 B | 441.00 | 452.00 | 453.00 | 441.00 | 461.00 | ok | -| [Q28](benchmarks/queries/query_28.sql) | 0 B | 961.00 | 919.25 | 919.50 | 915.00 | 923.00 | ok | -| [Q29](benchmarks/queries/query_29.sql) | 0 B | 5.00 | 7.00 | 7.50 | 4.00 | 9.00 | ok | -| [Q30](benchmarks/queries/query_30.sql) | 0 B | 40.00 | 37.75 | 38.00 | 36.00 | 39.00 | ok | -| [Q31](benchmarks/queries/query_31.sql) | 0 B | 50.00 | 55.00 | 56.00 | 50.00 | 58.00 | ok | -| [Q32](benchmarks/queries/query_32.sql) | 0 B | 626.00 | 820.25 | 682.50 | 668.00 | 1248.00 | ok | -| [Q33](benchmarks/queries/query_33.sql) | 0 B | 314.00 | 351.75 | 347.50 | 340.00 | 372.00 | ok | -| [Q34](benchmarks/queries/query_34.sql) | 0 B | 302.00 | 333.25 | 333.50 | 328.00 | 338.00 | ok | -| [Q35](benchmarks/queries/query_35.sql) | 0 B | 20.00 | 21.25 | 20.50 | 19.00 | 25.00 | ok | -| [Q36](benchmarks/queries/query_36.sql) | 0 B | 351.00 | 379.75 | 369.00 | 363.00 | 418.00 | ok | -| [Q37](benchmarks/queries/query_37.sql) | 0 B | 233.00 | 245.00 | 240.00 | 240.00 | 260.00 | ok | -| [Q38](benchmarks/queries/query_38.sql) | 0 B | 68.00 | 71.75 | 70.50 | 69.00 | 77.00 | ok | -| [Q39](benchmarks/queries/query_39.sql) | 0 B | 460.00 | 475.25 | 461.00 | 459.00 | 520.00 | ok | -| [Q40](benchmarks/queries/query_40.sql) | 0 B | 72.00 | 64.25 | 64.50 | 60.00 | 68.00 | ok | -| [Q41](benchmarks/queries/query_41.sql) | 0 B | 31.00 | 32.50 | 32.50 | 30.00 | 35.00 | ok | -| [Q42](benchmarks/queries/query_42.sql) | 0 B | 64.00 | 65.00 | 64.50 | 64.00 | 67.00 | ok | +| [Q07](benchmarks/queries/query_7.sql) | 0 B | 18.00 | 7.75 | 8.00 | 7.00 | 8.00 | ok | +| [Q08](benchmarks/queries/query_8.sql) | 0 B | 213.00 | 189.25 | 189.50 | 186.00 | 192.00 | ok | +| [Q09](benchmarks/queries/query_9.sql) | 0 B | 257.00 | 205.25 | 205.00 | 204.00 | 207.00 | ok | +| [Q10](benchmarks/queries/query_10.sql) | 0 B | 296.00 | 262.25 | 260.50 | 260.00 | 268.00 | ok | +| [Q11](benchmarks/queries/query_11.sql) | 0 B | 306.00 | 269.50 | 271.00 | 262.00 | 274.00 | ok | +| [Q12](benchmarks/queries/query_12.sql) | 0 B | 260.00 | 224.25 | 223.00 | 221.00 | 230.00 | ok | +| [Q13](benchmarks/queries/query_13.sql) | 0 B | 298.00 | 253.00 | 252.50 | 251.00 | 256.00 | ok | +| [Q14](benchmarks/queries/query_14.sql) | 0 B | 257.00 | 226.00 | 226.00 | 225.00 | 227.00 | ok | +| [Q15](benchmarks/queries/query_15.sql) | 0 B | 108.00 | 89.75 | 89.50 | 88.00 | 92.00 | ok | +| [Q16](benchmarks/queries/query_16.sql) | 0 B | 193.00 | 143.00 | 143.00 | 142.00 | 144.00 | ok | +| [Q17](benchmarks/queries/query_17.sql) | 0 B | 216.00 | 157.75 | 157.00 | 154.00 | 163.00 | ok | +| [Q18](benchmarks/queries/query_18.sql) | 0 B | 589.00 | 517.50 | 516.00 | 509.00 | 529.00 | ok | +| [Q19](benchmarks/queries/query_19.sql) | 0 B | 3.00 | <1 | <1 | <1 | <1 | ok | +| [Q20](benchmarks/queries/query_20.sql) | 0 B | 202.00 | 136.25 | 136.50 | 135.00 | 137.00 | ok | +| [Q21](benchmarks/queries/query_21.sql) | 0 B | 245.00 | 156.75 | 156.00 | 156.00 | 159.00 | ok | +| [Q22](benchmarks/queries/query_22.sql) | 0 B | 383.00 | 284.50 | 280.00 | 272.00 | 306.00 | ok | +| [Q23](benchmarks/queries/query_23.sql) | 0 B | 365.00 | 241.25 | 239.00 | 238.00 | 249.00 | ok | +| [Q24](benchmarks/queries/query_24.sql) | 0 B | 101.00 | 38.75 | 39.00 | 38.00 | 39.00 | ok | +| [Q25](benchmarks/queries/query_25.sql) | 0 B | 65.00 | 36.50 | 36.50 | 36.00 | 37.00 | ok | +| [Q26](benchmarks/queries/query_26.sql) | 0 B | 103.00 | 39.75 | 39.50 | 39.00 | 41.00 | ok | +| [Q27](benchmarks/queries/query_27.sql) | 0 B | 534.00 | 454.75 | 450.50 | 447.00 | 471.00 | ok | +| [Q28](benchmarks/queries/query_28.sql) | 0 B | 993.00 | 940.75 | 940.00 | 934.00 | 949.00 | ok | +| [Q29](benchmarks/queries/query_29.sql) | 0 B | 17.00 | 4.75 | 5.00 | 4.00 | 5.00 | ok | +| [Q30](benchmarks/queries/query_30.sql) | 0 B | 99.00 | 37.75 | 37.50 | 37.00 | 39.00 | ok | +| [Q31](benchmarks/queries/query_31.sql) | 0 B | 161.00 | 53.50 | 52.50 | 51.00 | 58.00 | ok | +| [Q32](benchmarks/queries/query_32.sql) | 0 B | 744.00 | 633.25 | 623.00 | 614.00 | 673.00 | ok | +| [Q33](benchmarks/queries/query_33.sql) | 0 B | 381.00 | 323.00 | 322.00 | 319.00 | 329.00 | ok | +| [Q34](benchmarks/queries/query_34.sql) | 0 B | 386.00 | 323.75 | 323.00 | 319.00 | 330.00 | ok | +| [Q35](benchmarks/queries/query_35.sql) | 0 B | 43.00 | 20.00 | 20.00 | 20.00 | 20.00 | ok | +| [Q36](benchmarks/queries/query_36.sql) | 0 B | 414.00 | 358.25 | 358.50 | 354.00 | 362.00 | ok | +| [Q37](benchmarks/queries/query_37.sql) | 0 B | 284.00 | 242.25 | 242.50 | 240.00 | 244.00 | ok | +| [Q38](benchmarks/queries/query_38.sql) | 0 B | 118.00 | 70.25 | 70.50 | 68.00 | 72.00 | ok | +| [Q39](benchmarks/queries/query_39.sql) | 0 B | 528.00 | 461.00 | 462.00 | 455.00 | 465.00 | ok | +| [Q40](benchmarks/queries/query_40.sql) | 0 B | 105.00 | 53.00 | 53.00 | 52.00 | 54.00 | ok | +| [Q41](benchmarks/queries/query_41.sql) | 0 B | 70.00 | 30.00 | 30.00 | 30.00 | 30.00 | ok | +| [Q42](benchmarks/queries/query_42.sql) | 0 B | 95.00 | 64.50 | 64.50 | 64.00 | 65.00 | ok | diff --git a/benchmarks/results/readme_benchmarks.csv b/benchmarks/results/readme_benchmarks.csv index 8cb6fe6..ca8927c 100644 --- a/benchmarks/results/readme_benchmarks.csv +++ b/benchmarks/results/readme_benchmarks.csv @@ -1,44 +1,44 @@ query_id,query_file,sql,first_run_ms,warm_avg_ms,warm_median_ms,warm_min_ms,warm_max_ms,output_csv_bytes,status,error 0,benchmarks/queries/query_0.sql,SELECT COUNT(*) FROM hits;,0.00,0.00,0.00,0.00,0.00,0,ok, -1,benchmarks/queries/query_1.sql,SELECT COUNT(*) FROM hits WHERE AdvEngineID <> 0;,9.00,4.25,4.00,4.00,5.00,0,ok, -2,benchmarks/queries/query_2.sql,"SELECT SUM(AdvEngineID), COUNT(*), AVG(ResolutionWidth) FROM hits;",11.00,8.50,8.50,7.00,10.00,0,ok, -3,benchmarks/queries/query_3.sql,SELECT AVG(UserID) FROM hits;,12.00,6.00,6.00,6.00,6.00,0,ok, -4,benchmarks/queries/query_4.sql,SELECT COUNT(DISTINCT UserID) FROM hits;,33.00,34.75,34.50,34.00,36.00,0,ok, -5,benchmarks/queries/query_5.sql,SELECT COUNT(DISTINCT SearchPhrase) FROM hits;,44.00,37.50,37.50,36.00,39.00,0,ok, +1,benchmarks/queries/query_1.sql,SELECT COUNT(*) FROM hits WHERE AdvEngineID <> 0;,16.00,4.00,4.00,4.00,4.00,0,ok, +2,benchmarks/queries/query_2.sql,"SELECT SUM(AdvEngineID), COUNT(*), AVG(ResolutionWidth) FROM hits;",31.00,7.00,7.00,7.00,7.00,0,ok, +3,benchmarks/queries/query_3.sql,SELECT AVG(UserID) FROM hits;,24.00,6.00,6.00,6.00,6.00,0,ok, +4,benchmarks/queries/query_4.sql,SELECT COUNT(DISTINCT UserID) FROM hits;,52.00,34.50,34.50,34.00,35.00,0,ok, +5,benchmarks/queries/query_5.sql,SELECT COUNT(DISTINCT SearchPhrase) FROM hits;,65.00,36.75,36.50,36.00,38.00,0,ok, 6,benchmarks/queries/query_6.sql,"SELECT MIN(EventDate), MAX(EventDate) FROM hits;",0.00,0.00,0.00,0.00,0.00,0,ok, -7,benchmarks/queries/query_7.sql,"SELECT AdvEngineID, COUNT(*) FROM hits WHERE AdvEngineID <> 0 GROUP BY AdvEngineID ORDER BY COUNT(*) DESC;",8.00,7.00,7.00,7.00,7.00,0,ok, -8,benchmarks/queries/query_8.sql,"SELECT RegionID, COUNT(DISTINCT UserID) AS u FROM hits GROUP BY RegionID ORDER BY u DESC LIMIT 10;",189.00,190.75,191.00,188.00,193.00,0,ok, -9,benchmarks/queries/query_9.sql,"SELECT RegionID, SUM(AdvEngineID), COUNT(*) AS c, AVG(ResolutionWidth), COUNT(DISTINCT UserID) FROM hits GROUP BY RegionID ORDER BY c DESC LIMIT 10;",202.00,225.00,214.00,209.00,263.00,0,ok, -10,benchmarks/queries/query_10.sql,"SELECT MobilePhoneModel, COUNT(DISTINCT UserID) AS u FROM hits WHERE MobilePhoneModel <> '' GROUP BY MobilePhoneModel ORDER BY u DESC LIMIT 10;",267.00,272.50,272.00,268.00,278.00,0,ok, -11,benchmarks/queries/query_11.sql,"SELECT MobilePhone, MobilePhoneModel, COUNT(DISTINCT UserID) AS u FROM hits WHERE MobilePhoneModel <> '' GROUP BY MobilePhone, MobilePhoneModel ORDER BY u DESC LIMIT 10;",264.00,268.50,267.50,266.00,273.00,0,ok, -12,benchmarks/queries/query_12.sql,"SELECT SearchPhrase, COUNT(*) AS c FROM hits WHERE SearchPhrase <> '' GROUP BY SearchPhrase ORDER BY c DESC LIMIT 10;",222.00,226.25,225.50,223.00,231.00,0,ok, -13,benchmarks/queries/query_13.sql,"SELECT SearchPhrase, COUNT(DISTINCT UserID) AS u FROM hits WHERE SearchPhrase <> '' GROUP BY SearchPhrase ORDER BY u DESC LIMIT 10;",253.00,258.25,259.00,252.00,263.00,0,ok, -14,benchmarks/queries/query_14.sql,"SELECT SearchEngineID, SearchPhrase, COUNT(*) AS c FROM hits WHERE SearchPhrase <> '' GROUP BY SearchEngineID, SearchPhrase ORDER BY c DESC LIMIT 10;",222.00,226.50,227.00,222.00,230.00,0,ok, -15,benchmarks/queries/query_15.sql,"SELECT UserID, COUNT(*) FROM hits GROUP BY UserID ORDER BY COUNT(*) DESC LIMIT 10;",90.00,98.75,99.00,91.00,106.00,0,ok, -16,benchmarks/queries/query_16.sql,"SELECT UserID, SearchPhrase, COUNT(*) FROM hits GROUP BY UserID, SearchPhrase ORDER BY COUNT(*) DESC LIMIT 10;",140.00,151.50,149.50,144.00,163.00,0,ok, -17,benchmarks/queries/query_17.sql,"SELECT UserID, SearchPhrase, COUNT(*) FROM hits GROUP BY UserID, SearchPhrase LIMIT 10;",151.00,162.75,162.50,158.00,168.00,0,ok, -18,benchmarks/queries/query_18.sql,"SELECT UserID, extract(minute FROM EventTime) AS m, SearchPhrase, COUNT(*) FROM hits GROUP BY UserID, m, SearchPhrase ORDER BY COUNT(*) DESC LIMIT 10;",512.00,511.25,510.00,504.00,521.00,0,ok, -19,benchmarks/queries/query_19.sql,SELECT UserID FROM hits WHERE UserID = 435090932899640449;,0.00,0.25,0.00,0.00,1.00,0,ok, -20,benchmarks/queries/query_20.sql,SELECT COUNT(*) FROM hits WHERE URL LIKE '%google%';,156.00,137.25,137.50,136.00,138.00,0,ok, -21,benchmarks/queries/query_21.sql,"SELECT SearchPhrase, MIN(URL), COUNT(*) AS c FROM hits WHERE URL LIKE '%google%' AND SearchPhrase <> '' GROUP BY SearchPhrase ORDER BY c DESC LIMIT 10;",152.00,155.50,155.00,154.00,158.00,0,ok, -22,benchmarks/queries/query_22.sql,"SELECT SearchPhrase, MIN(URL), MIN(Title), COUNT(*) AS c, COUNT(DISTINCT UserID) FROM hits WHERE Title LIKE '%Google%' AND URL NOT LIKE '%.google.%' AND SearchPhrase <> '' GROUP BY SearchPhrase ORDER BY c DESC LIMIT 10;",312.00,285.50,286.00,282.00,288.00,0,ok, -23,benchmarks/queries/query_23.sql,SELECT * FROM hits WHERE URL LIKE '%google%' ORDER BY EventTime LIMIT 10;,258.00,245.00,245.00,238.00,252.00,0,ok, -24,benchmarks/queries/query_24.sql,SELECT SearchPhrase FROM hits WHERE SearchPhrase <> '' ORDER BY EventTime LIMIT 10;,36.00,38.25,39.00,36.00,39.00,0,ok, -25,benchmarks/queries/query_25.sql,SELECT SearchPhrase FROM hits WHERE SearchPhrase <> '' ORDER BY SearchPhrase LIMIT 10;,35.00,36.25,35.00,35.00,40.00,0,ok, -26,benchmarks/queries/query_26.sql,"SELECT SearchPhrase FROM hits WHERE SearchPhrase <> '' ORDER BY EventTime, SearchPhrase LIMIT 10;",37.00,41.25,38.00,37.00,52.00,0,ok, -27,benchmarks/queries/query_27.sql,"SELECT CounterID, AVG(STRLEN(URL)) AS l, COUNT(*) AS c FROM hits WHERE URL <> '' GROUP BY CounterID HAVING COUNT(*) > 100000 ORDER BY l DESC LIMIT 25;",441.00,452.00,453.00,441.00,461.00,0,ok, -28,benchmarks/queries/query_28.sql,"SELECT REGEXP_REPLACE(Referer, '^https?://(?:www\.)?([^/]+)/.*$', '\1') AS k, AVG(STRLEN(Referer)) AS l, COUNT(*) AS c, MIN(Referer) FROM hits WHERE Referer <> '' GROUP BY k HAVING COUNT(*) > 100000 ORDER BY l DESC LIMIT 25;",961.00,919.25,919.50,915.00,923.00,0,ok, -29,benchmarks/queries/query_29.sql,"SELECT SUM(ResolutionWidth), SUM(ResolutionWidth + 1), SUM(ResolutionWidth + 2), SUM(ResolutionWidth + 3), SUM(ResolutionWidth + 4), SUM(ResolutionWidth + 5), SUM(ResolutionWidth + 6), SUM(ResolutionWidth + 7), SUM(ResolutionWidth + 8), SUM(ResolutionWidth + 9), SUM(ResolutionWidth + 10), SUM(ResolutionWidth + 11), SUM(ResolutionWidth + 12), SUM(ResolutionWidth + 13), SUM(ResolutionWidth + 14), SUM(ResolutionWidth + 15), SUM(ResolutionWidth + 16), SUM(ResolutionWidth + 17), SUM(ResolutionWidth + 18), SUM(ResolutionWidth + 19), SUM(ResolutionWidth + 20), SUM(ResolutionWidth + 21), SUM(ResolutionWidth + 22), SUM(ResolutionWidth + 23), SUM(ResolutionWidth + 24), SUM(ResolutionWidth + 25), SUM(ResolutionWidth + 26), SUM(ResolutionWidth + 27), SUM(ResolutionWidth + 28), SUM(ResolutionWidth + 29), SUM(ResolutionWidth + 30), SUM(ResolutionWidth + 31), SUM(ResolutionWidth + 32), SUM(ResolutionWidth + 33), SUM(ResolutionWidth + 34), SUM(ResolutionWidth + 35), SUM(ResolutionWidth + 36), SUM(ResolutionWidth + 37), SUM(ResolutionWidth + 38), SUM(ResolutionWidth + 39), SUM(ResolutionWidth + 40), SUM(ResolutionWidth + 41), SUM(ResolutionWidth + 42), SUM(ResolutionWidth + 43), SUM(ResolutionWidth + 44), SUM(ResolutionWidth + 45), SUM(ResolutionWidth + 46), SUM(ResolutionWidth + 47), SUM(ResolutionWidth + 48), SUM(ResolutionWidth + 49), SUM(ResolutionWidth + 50), SUM(ResolutionWidth + 51), SUM(ResolutionWidth + 52), SUM(ResolutionWidth + 53), SUM(ResolutionWidth + 54), SUM(ResolutionWidth + 55), SUM(ResolutionWidth + 56), SUM(ResolutionWidth + 57), SUM(ResolutionWidth + 58), SUM(ResolutionWidth + 59), SUM(ResolutionWidth + 60), SUM(ResolutionWidth + 61), SUM(ResolutionWidth + 62), SUM(ResolutionWidth + 63), SUM(ResolutionWidth + 64), SUM(ResolutionWidth + 65), SUM(ResolutionWidth + 66), SUM(ResolutionWidth + 67), SUM(ResolutionWidth + 68), SUM(ResolutionWidth + 69), SUM(ResolutionWidth + 70), SUM(ResolutionWidth + 71), SUM(ResolutionWidth + 72), SUM(ResolutionWidth + 73), SUM(ResolutionWidth + 74), SUM(ResolutionWidth + 75), SUM(ResolutionWidth + 76), SUM(ResolutionWidth + 77), SUM(ResolutionWidth + 78), SUM(ResolutionWidth + 79), SUM(ResolutionWidth + 80), SUM(ResolutionWidth + 81), SUM(ResolutionWidth + 82), SUM(ResolutionWidth + 83), SUM(ResolutionWidth + 84), SUM(ResolutionWidth + 85), SUM(ResolutionWidth + 86), SUM(ResolutionWidth + 87), SUM(ResolutionWidth + 88), SUM(ResolutionWidth + 89) FROM hits;",5.00,7.00,7.50,4.00,9.00,0,ok, -30,benchmarks/queries/query_30.sql,"SELECT SearchEngineID, ClientIP, COUNT(*) AS c, SUM(IsRefresh), AVG(ResolutionWidth) FROM hits WHERE SearchPhrase <> '' GROUP BY SearchEngineID, ClientIP ORDER BY c DESC LIMIT 10;",40.00,37.75,38.00,36.00,39.00,0,ok, -31,benchmarks/queries/query_31.sql,"SELECT WatchID, ClientIP, COUNT(*) AS c, SUM(IsRefresh), AVG(ResolutionWidth) FROM hits WHERE SearchPhrase <> '' GROUP BY WatchID, ClientIP ORDER BY c DESC LIMIT 10;",50.00,55.00,56.00,50.00,58.00,0,ok, -32,benchmarks/queries/query_32.sql,"SELECT WatchID, ClientIP, COUNT(*) AS c, SUM(IsRefresh), AVG(ResolutionWidth) FROM hits GROUP BY WatchID, ClientIP ORDER BY c DESC LIMIT 10;",626.00,820.25,682.50,668.00,1248.00,0,ok, -33,benchmarks/queries/query_33.sql,"SELECT URL, COUNT(*) AS c FROM hits GROUP BY URL ORDER BY c DESC LIMIT 10;",314.00,351.75,347.50,340.00,372.00,0,ok, -34,benchmarks/queries/query_34.sql,"SELECT 1, URL, COUNT(*) AS c FROM hits GROUP BY 1, URL ORDER BY c DESC LIMIT 10;",302.00,333.25,333.50,328.00,338.00,0,ok, -35,benchmarks/queries/query_35.sql,"SELECT ClientIP, ClientIP - 1, ClientIP - 2, ClientIP - 3, COUNT(*) AS c FROM hits GROUP BY ClientIP, ClientIP - 1, ClientIP - 2, ClientIP - 3 ORDER BY c DESC LIMIT 10;",20.00,21.25,20.50,19.00,25.00,0,ok, -36,benchmarks/queries/query_36.sql,"SELECT URL, COUNT(*) AS PageViews FROM hits WHERE CounterID = 62 AND EventDate >= '2013-07-01' AND EventDate <= '2013-07-31' AND DontCountHits = 0 AND IsRefresh = 0 AND URL <> '' GROUP BY URL ORDER BY PageViews DESC LIMIT 10;",351.00,379.75,369.00,363.00,418.00,0,ok, -37,benchmarks/queries/query_37.sql,"SELECT Title, COUNT(*) AS PageViews FROM hits WHERE CounterID = 62 AND EventDate >= '2013-07-01' AND EventDate <= '2013-07-31' AND DontCountHits = 0 AND IsRefresh = 0 AND Title <> '' GROUP BY Title ORDER BY PageViews DESC LIMIT 10;",233.00,245.00,240.00,240.00,260.00,0,ok, -38,benchmarks/queries/query_38.sql,"SELECT URL, COUNT(*) AS PageViews FROM hits WHERE CounterID = 62 AND EventDate >= '2013-07-01' AND EventDate <= '2013-07-31' AND IsRefresh = 0 AND IsLink <> 0 AND IsDownload = 0 GROUP BY URL ORDER BY PageViews DESC LIMIT 10 OFFSET 1000;",68.00,71.75,70.50,69.00,77.00,0,ok, -39,benchmarks/queries/query_39.sql,"SELECT TraficSourceID, SearchEngineID, AdvEngineID, CASE WHEN (SearchEngineID = 0 AND AdvEngineID = 0) THEN Referer ELSE '' END AS Src, URL AS Dst, COUNT(*) AS PageViews FROM hits WHERE CounterID = 62 AND EventDate >= '2013-07-01' AND EventDate <= '2013-07-31' AND IsRefresh = 0 GROUP BY TraficSourceID, SearchEngineID, AdvEngineID, Src, Dst ORDER BY PageViews DESC LIMIT 10 OFFSET 1000;",460.00,475.25,461.00,459.00,520.00,0,ok, -40,benchmarks/queries/query_40.sql,"SELECT URLHash, EventDate, COUNT(*) AS PageViews FROM hits WHERE CounterID = 62 AND EventDate >= '2013-07-01' AND EventDate <= '2013-07-31' AND IsRefresh = 0 AND TraficSourceID IN (-1, 6) AND RefererHash = 3594120000172545465 GROUP BY URLHash, EventDate ORDER BY PageViews DESC LIMIT 10 OFFSET 100;",72.00,64.25,64.50,60.00,68.00,0,ok, -41,benchmarks/queries/query_41.sql,"SELECT WindowClientWidth, WindowClientHeight, COUNT(*) AS PageViews FROM hits WHERE CounterID = 62 AND EventDate >= '2013-07-01' AND EventDate <= '2013-07-31' AND IsRefresh = 0 AND DontCountHits = 0 AND URLHash = 2868770270353813622 GROUP BY WindowClientWidth, WindowClientHeight ORDER BY PageViews DESC LIMIT 10 OFFSET 10000;",31.00,32.50,32.50,30.00,35.00,0,ok, -42,benchmarks/queries/query_42.sql,"SELECT DATE_TRUNC('minute', EventTime) AS M, COUNT(*) AS PageViews FROM hits WHERE CounterID = 62 AND EventDate >= '2013-07-14' AND EventDate <= '2013-07-15' AND IsRefresh = 0 AND DontCountHits = 0 GROUP BY DATE_TRUNC('minute', EventTime) ORDER BY DATE_TRUNC('minute', EventTime) LIMIT 10 OFFSET 1000;",64.00,65.00,64.50,64.00,67.00,0,ok, +7,benchmarks/queries/query_7.sql,"SELECT AdvEngineID, COUNT(*) FROM hits WHERE AdvEngineID <> 0 GROUP BY AdvEngineID ORDER BY COUNT(*) DESC;",18.00,7.75,8.00,7.00,8.00,0,ok, +8,benchmarks/queries/query_8.sql,"SELECT RegionID, COUNT(DISTINCT UserID) AS u FROM hits GROUP BY RegionID ORDER BY u DESC LIMIT 10;",213.00,189.25,189.50,186.00,192.00,0,ok, +9,benchmarks/queries/query_9.sql,"SELECT RegionID, SUM(AdvEngineID), COUNT(*) AS c, AVG(ResolutionWidth), COUNT(DISTINCT UserID) FROM hits GROUP BY RegionID ORDER BY c DESC LIMIT 10;",257.00,205.25,205.00,204.00,207.00,0,ok, +10,benchmarks/queries/query_10.sql,"SELECT MobilePhoneModel, COUNT(DISTINCT UserID) AS u FROM hits WHERE MobilePhoneModel <> '' GROUP BY MobilePhoneModel ORDER BY u DESC LIMIT 10;",296.00,262.25,260.50,260.00,268.00,0,ok, +11,benchmarks/queries/query_11.sql,"SELECT MobilePhone, MobilePhoneModel, COUNT(DISTINCT UserID) AS u FROM hits WHERE MobilePhoneModel <> '' GROUP BY MobilePhone, MobilePhoneModel ORDER BY u DESC LIMIT 10;",306.00,269.50,271.00,262.00,274.00,0,ok, +12,benchmarks/queries/query_12.sql,"SELECT SearchPhrase, COUNT(*) AS c FROM hits WHERE SearchPhrase <> '' GROUP BY SearchPhrase ORDER BY c DESC LIMIT 10;",260.00,224.25,223.00,221.00,230.00,0,ok, +13,benchmarks/queries/query_13.sql,"SELECT SearchPhrase, COUNT(DISTINCT UserID) AS u FROM hits WHERE SearchPhrase <> '' GROUP BY SearchPhrase ORDER BY u DESC LIMIT 10;",298.00,253.00,252.50,251.00,256.00,0,ok, +14,benchmarks/queries/query_14.sql,"SELECT SearchEngineID, SearchPhrase, COUNT(*) AS c FROM hits WHERE SearchPhrase <> '' GROUP BY SearchEngineID, SearchPhrase ORDER BY c DESC LIMIT 10;",257.00,226.00,226.00,225.00,227.00,0,ok, +15,benchmarks/queries/query_15.sql,"SELECT UserID, COUNT(*) FROM hits GROUP BY UserID ORDER BY COUNT(*) DESC LIMIT 10;",108.00,89.75,89.50,88.00,92.00,0,ok, +16,benchmarks/queries/query_16.sql,"SELECT UserID, SearchPhrase, COUNT(*) FROM hits GROUP BY UserID, SearchPhrase ORDER BY COUNT(*) DESC LIMIT 10;",193.00,143.00,143.00,142.00,144.00,0,ok, +17,benchmarks/queries/query_17.sql,"SELECT UserID, SearchPhrase, COUNT(*) FROM hits GROUP BY UserID, SearchPhrase LIMIT 10;",216.00,157.75,157.00,154.00,163.00,0,ok, +18,benchmarks/queries/query_18.sql,"SELECT UserID, extract(minute FROM EventTime) AS m, SearchPhrase, COUNT(*) FROM hits GROUP BY UserID, m, SearchPhrase ORDER BY COUNT(*) DESC LIMIT 10;",589.00,517.50,516.00,509.00,529.00,0,ok, +19,benchmarks/queries/query_19.sql,SELECT UserID FROM hits WHERE UserID = 435090932899640449;,3.00,0.00,0.00,0.00,0.00,0,ok, +20,benchmarks/queries/query_20.sql,SELECT COUNT(*) FROM hits WHERE URL LIKE '%google%';,202.00,136.25,136.50,135.00,137.00,0,ok, +21,benchmarks/queries/query_21.sql,"SELECT SearchPhrase, MIN(URL), COUNT(*) AS c FROM hits WHERE URL LIKE '%google%' AND SearchPhrase <> '' GROUP BY SearchPhrase ORDER BY c DESC LIMIT 10;",245.00,156.75,156.00,156.00,159.00,0,ok, +22,benchmarks/queries/query_22.sql,"SELECT SearchPhrase, MIN(URL), MIN(Title), COUNT(*) AS c, COUNT(DISTINCT UserID) FROM hits WHERE Title LIKE '%Google%' AND URL NOT LIKE '%.google.%' AND SearchPhrase <> '' GROUP BY SearchPhrase ORDER BY c DESC LIMIT 10;",383.00,284.50,280.00,272.00,306.00,0,ok, +23,benchmarks/queries/query_23.sql,SELECT * FROM hits WHERE URL LIKE '%google%' ORDER BY EventTime LIMIT 10;,365.00,241.25,239.00,238.00,249.00,0,ok, +24,benchmarks/queries/query_24.sql,SELECT SearchPhrase FROM hits WHERE SearchPhrase <> '' ORDER BY EventTime LIMIT 10;,101.00,38.75,39.00,38.00,39.00,0,ok, +25,benchmarks/queries/query_25.sql,SELECT SearchPhrase FROM hits WHERE SearchPhrase <> '' ORDER BY SearchPhrase LIMIT 10;,65.00,36.50,36.50,36.00,37.00,0,ok, +26,benchmarks/queries/query_26.sql,"SELECT SearchPhrase FROM hits WHERE SearchPhrase <> '' ORDER BY EventTime, SearchPhrase LIMIT 10;",103.00,39.75,39.50,39.00,41.00,0,ok, +27,benchmarks/queries/query_27.sql,"SELECT CounterID, AVG(STRLEN(URL)) AS l, COUNT(*) AS c FROM hits WHERE URL <> '' GROUP BY CounterID HAVING COUNT(*) > 100000 ORDER BY l DESC LIMIT 25;",534.00,454.75,450.50,447.00,471.00,0,ok, +28,benchmarks/queries/query_28.sql,"SELECT REGEXP_REPLACE(Referer, '^https?://(?:www\.)?([^/]+)/.*$', '\1') AS k, AVG(STRLEN(Referer)) AS l, COUNT(*) AS c, MIN(Referer) FROM hits WHERE Referer <> '' GROUP BY k HAVING COUNT(*) > 100000 ORDER BY l DESC LIMIT 25;",993.00,940.75,940.00,934.00,949.00,0,ok, +29,benchmarks/queries/query_29.sql,"SELECT SUM(ResolutionWidth), SUM(ResolutionWidth + 1), SUM(ResolutionWidth + 2), SUM(ResolutionWidth + 3), SUM(ResolutionWidth + 4), SUM(ResolutionWidth + 5), SUM(ResolutionWidth + 6), SUM(ResolutionWidth + 7), SUM(ResolutionWidth + 8), SUM(ResolutionWidth + 9), SUM(ResolutionWidth + 10), SUM(ResolutionWidth + 11), SUM(ResolutionWidth + 12), SUM(ResolutionWidth + 13), SUM(ResolutionWidth + 14), SUM(ResolutionWidth + 15), SUM(ResolutionWidth + 16), SUM(ResolutionWidth + 17), SUM(ResolutionWidth + 18), SUM(ResolutionWidth + 19), SUM(ResolutionWidth + 20), SUM(ResolutionWidth + 21), SUM(ResolutionWidth + 22), SUM(ResolutionWidth + 23), SUM(ResolutionWidth + 24), SUM(ResolutionWidth + 25), SUM(ResolutionWidth + 26), SUM(ResolutionWidth + 27), SUM(ResolutionWidth + 28), SUM(ResolutionWidth + 29), SUM(ResolutionWidth + 30), SUM(ResolutionWidth + 31), SUM(ResolutionWidth + 32), SUM(ResolutionWidth + 33), SUM(ResolutionWidth + 34), SUM(ResolutionWidth + 35), SUM(ResolutionWidth + 36), SUM(ResolutionWidth + 37), SUM(ResolutionWidth + 38), SUM(ResolutionWidth + 39), SUM(ResolutionWidth + 40), SUM(ResolutionWidth + 41), SUM(ResolutionWidth + 42), SUM(ResolutionWidth + 43), SUM(ResolutionWidth + 44), SUM(ResolutionWidth + 45), SUM(ResolutionWidth + 46), SUM(ResolutionWidth + 47), SUM(ResolutionWidth + 48), SUM(ResolutionWidth + 49), SUM(ResolutionWidth + 50), SUM(ResolutionWidth + 51), SUM(ResolutionWidth + 52), SUM(ResolutionWidth + 53), SUM(ResolutionWidth + 54), SUM(ResolutionWidth + 55), SUM(ResolutionWidth + 56), SUM(ResolutionWidth + 57), SUM(ResolutionWidth + 58), SUM(ResolutionWidth + 59), SUM(ResolutionWidth + 60), SUM(ResolutionWidth + 61), SUM(ResolutionWidth + 62), SUM(ResolutionWidth + 63), SUM(ResolutionWidth + 64), SUM(ResolutionWidth + 65), SUM(ResolutionWidth + 66), SUM(ResolutionWidth + 67), SUM(ResolutionWidth + 68), SUM(ResolutionWidth + 69), SUM(ResolutionWidth + 70), SUM(ResolutionWidth + 71), SUM(ResolutionWidth + 72), SUM(ResolutionWidth + 73), SUM(ResolutionWidth + 74), SUM(ResolutionWidth + 75), SUM(ResolutionWidth + 76), SUM(ResolutionWidth + 77), SUM(ResolutionWidth + 78), SUM(ResolutionWidth + 79), SUM(ResolutionWidth + 80), SUM(ResolutionWidth + 81), SUM(ResolutionWidth + 82), SUM(ResolutionWidth + 83), SUM(ResolutionWidth + 84), SUM(ResolutionWidth + 85), SUM(ResolutionWidth + 86), SUM(ResolutionWidth + 87), SUM(ResolutionWidth + 88), SUM(ResolutionWidth + 89) FROM hits;",17.00,4.75,5.00,4.00,5.00,0,ok, +30,benchmarks/queries/query_30.sql,"SELECT SearchEngineID, ClientIP, COUNT(*) AS c, SUM(IsRefresh), AVG(ResolutionWidth) FROM hits WHERE SearchPhrase <> '' GROUP BY SearchEngineID, ClientIP ORDER BY c DESC LIMIT 10;",99.00,37.75,37.50,37.00,39.00,0,ok, +31,benchmarks/queries/query_31.sql,"SELECT WatchID, ClientIP, COUNT(*) AS c, SUM(IsRefresh), AVG(ResolutionWidth) FROM hits WHERE SearchPhrase <> '' GROUP BY WatchID, ClientIP ORDER BY c DESC LIMIT 10;",161.00,53.50,52.50,51.00,58.00,0,ok, +32,benchmarks/queries/query_32.sql,"SELECT WatchID, ClientIP, COUNT(*) AS c, SUM(IsRefresh), AVG(ResolutionWidth) FROM hits GROUP BY WatchID, ClientIP ORDER BY c DESC LIMIT 10;",744.00,633.25,623.00,614.00,673.00,0,ok, +33,benchmarks/queries/query_33.sql,"SELECT URL, COUNT(*) AS c FROM hits GROUP BY URL ORDER BY c DESC LIMIT 10;",381.00,323.00,322.00,319.00,329.00,0,ok, +34,benchmarks/queries/query_34.sql,"SELECT 1, URL, COUNT(*) AS c FROM hits GROUP BY 1, URL ORDER BY c DESC LIMIT 10;",386.00,323.75,323.00,319.00,330.00,0,ok, +35,benchmarks/queries/query_35.sql,"SELECT ClientIP, ClientIP - 1, ClientIP - 2, ClientIP - 3, COUNT(*) AS c FROM hits GROUP BY ClientIP, ClientIP - 1, ClientIP - 2, ClientIP - 3 ORDER BY c DESC LIMIT 10;",43.00,20.00,20.00,20.00,20.00,0,ok, +36,benchmarks/queries/query_36.sql,"SELECT URL, COUNT(*) AS PageViews FROM hits WHERE CounterID = 62 AND EventDate >= '2013-07-01' AND EventDate <= '2013-07-31' AND DontCountHits = 0 AND IsRefresh = 0 AND URL <> '' GROUP BY URL ORDER BY PageViews DESC LIMIT 10;",414.00,358.25,358.50,354.00,362.00,0,ok, +37,benchmarks/queries/query_37.sql,"SELECT Title, COUNT(*) AS PageViews FROM hits WHERE CounterID = 62 AND EventDate >= '2013-07-01' AND EventDate <= '2013-07-31' AND DontCountHits = 0 AND IsRefresh = 0 AND Title <> '' GROUP BY Title ORDER BY PageViews DESC LIMIT 10;",284.00,242.25,242.50,240.00,244.00,0,ok, +38,benchmarks/queries/query_38.sql,"SELECT URL, COUNT(*) AS PageViews FROM hits WHERE CounterID = 62 AND EventDate >= '2013-07-01' AND EventDate <= '2013-07-31' AND IsRefresh = 0 AND IsLink <> 0 AND IsDownload = 0 GROUP BY URL ORDER BY PageViews DESC LIMIT 10 OFFSET 1000;",118.00,70.25,70.50,68.00,72.00,0,ok, +39,benchmarks/queries/query_39.sql,"SELECT TraficSourceID, SearchEngineID, AdvEngineID, CASE WHEN (SearchEngineID = 0 AND AdvEngineID = 0) THEN Referer ELSE '' END AS Src, URL AS Dst, COUNT(*) AS PageViews FROM hits WHERE CounterID = 62 AND EventDate >= '2013-07-01' AND EventDate <= '2013-07-31' AND IsRefresh = 0 GROUP BY TraficSourceID, SearchEngineID, AdvEngineID, Src, Dst ORDER BY PageViews DESC LIMIT 10 OFFSET 1000;",528.00,461.00,462.00,455.00,465.00,0,ok, +40,benchmarks/queries/query_40.sql,"SELECT URLHash, EventDate, COUNT(*) AS PageViews FROM hits WHERE CounterID = 62 AND EventDate >= '2013-07-01' AND EventDate <= '2013-07-31' AND IsRefresh = 0 AND TraficSourceID IN (-1, 6) AND RefererHash = 3594120000172545465 GROUP BY URLHash, EventDate ORDER BY PageViews DESC LIMIT 10 OFFSET 100;",105.00,53.00,53.00,52.00,54.00,0,ok, +41,benchmarks/queries/query_41.sql,"SELECT WindowClientWidth, WindowClientHeight, COUNT(*) AS PageViews FROM hits WHERE CounterID = 62 AND EventDate >= '2013-07-01' AND EventDate <= '2013-07-31' AND IsRefresh = 0 AND DontCountHits = 0 AND URLHash = 2868770270353813622 GROUP BY WindowClientWidth, WindowClientHeight ORDER BY PageViews DESC LIMIT 10 OFFSET 10000;",70.00,30.00,30.00,30.00,30.00,0,ok, +42,benchmarks/queries/query_42.sql,"SELECT DATE_TRUNC('minute', EventTime) AS M, COUNT(*) AS PageViews FROM hits WHERE CounterID = 62 AND EventDate >= '2013-07-14' AND EventDate <= '2013-07-15' AND IsRefresh = 0 AND DontCountHits = 0 GROUP BY DATE_TRUNC('minute', EventTime) ORDER BY DATE_TRUNC('minute', EventTime) LIMIT 10 OFFSET 1000;",95.00,64.50,64.50,64.00,65.00,0,ok, From 965de38255139a736728cd5222c9a27831bbe108 Mon Sep 17 00:00:00 2001 From: ik4rd Date: Mon, 1 Jun 2026 01:18:41 +0300 Subject: [PATCH 14/16] fix: lol, hash issue --- benchmarks/support/query_runners_fast.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/benchmarks/support/query_runners_fast.cpp b/benchmarks/support/query_runners_fast.cpp index 9417cfd..866252c 100644 --- a/benchmarks/support/query_runners_fast.cpp +++ b/benchmarks/support/query_runners_fast.cpp @@ -26,7 +26,7 @@ struct Int128Hash { const UInt128 raw = static_cast(value); const uint64_t low = static_cast(raw); const uint64_t high = static_cast(raw >> 64); - return std::hash{}(low ^ high + hash_magic + (low << 6) + (low >> 2)); + return std::hash{}(low ^ (high + hash_magic + (low << 6) + (low >> 2))); } }; @@ -34,7 +34,7 @@ struct Int128PairHash { size_t operator()(const std::pair& value) const noexcept { const size_t lhs = Int128Hash{}(value.first); const size_t rhs = Int128Hash{}(value.second); - return lhs ^ rhs + hash_magic + (lhs << 6) + (lhs >> 2); + return lhs ^ (rhs + hash_magic + (lhs << 6) + (lhs >> 2)); } }; From 840bea1c86b256272e496f2a6c54438e07ae960c Mon Sep 17 00:00:00 2001 From: ik4rd Date: Mon, 1 Jun 2026 01:23:29 +0300 Subject: [PATCH 15/16] fix: benchmark issue fixed --- .github/workflows/cmake-single-platform.yml | 16 ++++++++++++---- 1 file changed, 12 insertions(+), 4 deletions(-) diff --git a/.github/workflows/cmake-single-platform.yml b/.github/workflows/cmake-single-platform.yml index b77e5dd..80ec2f7 100644 --- a/.github/workflows/cmake-single-platform.yml +++ b/.github/workflows/cmake-single-platform.yml @@ -42,8 +42,12 @@ jobs: - name: Benchmark Queries run: | - cd benchmarks - ../build/benchmarks/benchmark_queries + if [ -f benchmarks/hits_sample.csv ]; then + ./build/benchmarks/benchmark_csv_to_columnar + ./build/benchmarks/benchmark_queries + else + echo "Skipping benchmark queries: benchmarks/hits_sample.csv is not available in this checkout." + fi sanitizers: runs-on: ubuntu-latest @@ -82,5 +86,9 @@ jobs: ASAN_OPTIONS: detect_leaks=1:strict_string_checks=1:check_initialization_order=1 UBSAN_OPTIONS: print_stacktrace=1:halt_on_error=1 run: | - cd benchmarks - ../build-sanitizers/benchmarks/benchmark_queries + if [ -f benchmarks/hits_sample.csv ]; then + ./build-sanitizers/benchmarks/benchmark_csv_to_columnar + ./build-sanitizers/benchmarks/benchmark_queries + else + echo "Skipping benchmark queries with sanitizers: benchmarks/hits_sample.csv is not available in this checkout." + fi From f2b5631542dc07d63eceb1f24551eb0c126ea9ca Mon Sep 17 00:00:00 2001 From: ik4rd Date: Mon, 1 Jun 2026 01:47:18 +0300 Subject: [PATCH 16/16] fix: ORDER BY issue with SELECT * in plain_select --- src/executor/operators_aggregate.cpp | 35 +++++++++++------ src/executor/operators_runtime.cpp | 5 +++ src/executor/query_planner.cpp | 49 ++++++++++++++++------- tests/test_executor.cpp | 59 ++++++++++++++++++++++++++++ 4 files changed, 123 insertions(+), 25 deletions(-) diff --git a/src/executor/operators_aggregate.cpp b/src/executor/operators_aggregate.cpp index 672e5c0..99664ce 100644 --- a/src/executor/operators_aggregate.cpp +++ b/src/executor/operators_aggregate.cpp @@ -646,10 +646,16 @@ class GroupAggOperator final : public Operator { finalized->int_values.reserve(group.states.size()); for (size_t i = 0; i < group.states.size(); ++i) { - std::string value = group.states[i].Finalize(); const ColumnType type = AggregateOutputType(bindings_[i].aggregate); - finalized->int_values.push_back(type == ColumnType::String ? 0 : ParseColumnValueAsInt128(type, value)); - finalized->values.push_back(std::move(value)); + if (type == ColumnType::String) { + finalized->int_values.push_back(0); + finalized->values.push_back(group.states[i].Finalize()); + continue; + } + + const Int128 value = group.states[i].FinalizeInt(type); + finalized->int_values.push_back(value); + finalized->values.push_back(FormatInt128Value(type, value)); } group.finalized_aggregates = std::move(finalized); @@ -839,10 +845,16 @@ class GroupAggTopKOperator final : public Operator { finalized->int_values.reserve(group.states.size()); for (size_t i = 0; i < group.states.size(); ++i) { - std::string value = group.states[i].Finalize(); const ColumnType type = AggregateOutputType(bindings_[i].aggregate); - finalized->int_values.push_back(type == ColumnType::String ? 0 : ParseColumnValueAsInt128(type, value)); - finalized->values.push_back(std::move(value)); + if (type == ColumnType::String) { + finalized->int_values.push_back(0); + finalized->values.push_back(group.states[i].Finalize()); + continue; + } + + const Int128 value = group.states[i].FinalizeInt(type); + finalized->int_values.push_back(value); + finalized->values.push_back(FormatInt128Value(type, value)); } group.finalized_aggregates = std::move(finalized); @@ -850,6 +862,9 @@ class GroupAggTopKOperator final : public Operator { } bool OrdersBefore(const GroupEntry* lhs, const GroupEntry* rhs) const { + const FinalizedAggregateValues& lhs_finalized = FinalizeAggregateValues(lhs->second); + const FinalizedAggregateValues& rhs_finalized = FinalizeAggregateValues(rhs->second); + for (const PlannedOrderBy& order : order_by_) { const PlannedSelectItem& item = select_items_[order.result_column_index]; std::strong_ordering comparison = std::strong_ordering::equal; @@ -857,11 +872,9 @@ class GroupAggTopKOperator final : public Operator { if (item.kind == SelectItemKind::GroupKey) { comparison = CompareGroupKeyValue(lhs->first.values[item.index], rhs->first.values[item.index]); } else { - comparison = - item.output_type == ColumnType::String - ? lhs->second.states[item.index].Finalize() <=> rhs->second.states[item.index].Finalize() - : lhs->second.states[item.index].FinalizeInt(item.output_type) <=> - rhs->second.states[item.index].FinalizeInt(item.output_type); + comparison = item.output_type == ColumnType::String + ? lhs_finalized.values[item.index] <=> rhs_finalized.values[item.index] + : lhs_finalized.int_values[item.index] <=> rhs_finalized.int_values[item.index]; } if (comparison != 0) { diff --git a/src/executor/operators_runtime.cpp b/src/executor/operators_runtime.cpp index 7f74691..ecffa19 100644 --- a/src/executor/operators_runtime.cpp +++ b/src/executor/operators_runtime.cpp @@ -257,6 +257,11 @@ struct RowRef { std::strong_ordering CompareColumnRows(const Column& lhs_column, const size_t lhs_row, const Column& rhs_column, const size_t rhs_row, const ColumnType type) { + if (type != ColumnType::String && lhs_column.Type() == ColumnType::String && rhs_column.Type() == ColumnType::String) { + return ParseColumnValueAsInt128(type, lhs_column.ValueAsString(lhs_row)) <=> + ParseColumnValueAsInt128(type, rhs_column.ValueAsString(rhs_row)); + } + if (type != ColumnType::String) { return lhs_column.ValueAsInt128(lhs_row) <=> rhs_column.ValueAsInt128(rhs_row); } diff --git a/src/executor/query_planner.cpp b/src/executor/query_planner.cpp index 0d3ef5b..619d6e6 100644 --- a/src/executor/query_planner.cpp +++ b/src/executor/query_planner.cpp @@ -6,6 +6,7 @@ #include "common/error.h" #include "common/parsing.h" #include "executor/comparison_utils.h" +#include "executor/operators_internal.h" #include "executor/query_utils.h" #include "executor/typed_value_utils.h" #include "io/columnar_batch.h" @@ -119,6 +120,21 @@ static ExprPtr ResolveGroupByExpression(const Query& query_ast, const ExprPtr& e return expr; } +static std::optional FindProjectedOrderColumn(const Schema& schema, const SelectItemSpec& item, + const ExprPtr& expression) { + for (size_t i = 0; i < schema.columns.size(); ++i) { + if (SameColumnName(schema.columns[i].name, item.output_name)) { + return i; + } + } + + if (expression && expression->kind == ExprKind::Column) { + return TryFindBatchColumn(schema, expression->column.name); + } + + return std::nullopt; +} + static void AddProjectionIndex(std::vector& indexes, const size_t index) { if (!std::ranges::contains(indexes, index)) { indexes.push_back(index); @@ -631,34 +647,24 @@ PlannedQuery PlanQuery(const Query& query, const std::unordered_mapkind == ExprKind::Star; + }); for (const auto& order_item : query.order_by) { - size_t column_index = 0; const auto selected = FindSelectItem(query, order_item.item); const ExprPtr order_expression = selected.has_value() ? query.select_items[*selected].expression : order_item.item.expression; const ColumnType type = ColumnTypeOf(query, schema, order_expression); CollectColumnsFromExpr(query, schema, order_expression, projection_indexes); - if (selected.has_value()) { - column_index = *selected; - } else if (order_expression && order_expression->kind == ExprKind::Column) { - column_index = planned.plain_select_items.size(); - } else { - throw Error::InvalidArgument("executor", "ORDER BY expression is not supported in plain SELECT"); - } - planned.order_by.push_back(PlannedOrderBy{ - .result_column_index = column_index, + .result_column_index = 0, .descending = order_item.descending, .value_type = type, }); } - const bool has_star_select = std::ranges::any_of(planned.plain_select_items, [](const SelectItemSpec& item) { - return item.expression && item.expression->kind == ExprKind::Star; - }); - for (const auto& order_item : query.order_by) { if (!order_item.item.expression || order_item.item.expression->kind == ExprKind::Star) { continue; @@ -687,6 +693,21 @@ PlannedQuery PlanQuery(const Query& query, const std::unordered_map projected_index = + FindProjectedOrderColumn(output_schema, query.order_by[i].item, order_expression); + + if (!projected_index.has_value()) { + throw Error::InvalidArgument("executor", "ORDER BY expression is not supported in plain SELECT"); + } + + planned.order_by[i].result_column_index = *projected_index; + } } else { for (const auto& order_item : query.order_by) { const std::optional result_column = FindSelectItem(query, order_item.item); diff --git a/tests/test_executor.cpp b/tests/test_executor.cpp index 53ad147..03dd56f 100644 --- a/tests/test_executor.cpp +++ b/tests/test_executor.cpp @@ -178,6 +178,65 @@ TEST(executor, supports_order_by_limit_top_k_in_result_order) { })); } +TEST(executor, supports_select_star_order_by_non_first_column) { + const Batch batch = BuildHitsTable("SELECT * FROM hits ORDER BY EventDate ASC LIMIT 3;"); + + EXPECT_EQ(BatchColumnNames(batch), + (std::vector{"AdvEngineID", "ResolutionWidth", "UserID", "RegionID", "MobilePhone", + "MobilePhoneModel", "SearchEngineID", "SearchPhrase", "EventDate"})); + EXPECT_EQ(BatchRows(batch), (std::vector>{ + {"7", "500", "5", "20", "true", "iPhone", "1", "gamma", "2024-01-01"}, + {"7", "200", "2", "20", "true", "iPhone", "1", "beta", "2024-01-03"}, + {"0", "100", "1", "10", "false", "", "1", "alpha", "2024-01-05"}, + })); +} + +TEST(executor, supports_plain_select_order_by_hidden_typed_column) { + const Batch batch = + BuildHitsTable("SELECT SearchPhrase FROM hits WHERE SearchPhrase != '' ORDER BY EventDate ASC LIMIT 3;"); + + EXPECT_EQ(BatchColumnNames(batch), (std::vector{"SearchPhrase", "EventDate"})); + EXPECT_EQ(BatchRows(batch), (std::vector>{ + {"gamma", "2024-01-01"}, + {"beta", "2024-01-03"}, + {"alpha", "2024-01-05"}, + })); +} + +TEST(executor, supports_clickbench_star_order_by_projected_column) { + const TempFile schema_file("executor_schema"); + const TempFile data_file("executor_data"); + const TempFile columnar_file("executor_columnar"); + + WriteRows(schema_file.Path(), { + {"WatchID", "int64"}, + {"EventTime", "timestamp"}, + {"URL", "string"}, + {"Title", "string"}, + {"SearchPhrase", "string"}, + }); + WriteRows(data_file.Path(), { + {"1", "2024-01-05 00:00:00", "http://example.com", "other", "skip"}, + {"2", "2024-01-03 00:00:00", "http://google.com/a", "first", "a"}, + {"3", "2024-01-01 00:00:00", "https://google.com/b", "second", "b"}, + {"4", "2024-01-04 00:00:00", "http://google.com/c", "third", "c"}, + }); + ConvertCsvToColumnar(schema_file.Path(), data_file.Path(), columnar_file.Path(), 2); + + Executor executor; + executor.RegisterTable("hits", columnar_file.Path()); + + auto result = executor.Execute("SELECT * FROM hits WHERE URL LIKE '%google%' ORDER BY EventTime ASC LIMIT 2;"); + ASSERT_TRUE(result.has_value()) << result.error().what(); + + EXPECT_EQ(BatchColumnNames(result.value()), + (std::vector{"WatchID", "EventTime", "URL", "Title"})); + EXPECT_EQ(BatchRows(result.value()), (std::vector>{ + {"3", "2024-01-01 00:00:00", "https://google.com/b", "second"}, + {"2", "2024-01-03 00:00:00", "http://google.com/a", "first"}, + })); +} + TEST(executor, supports_multiple_aggregates_with_alias_and_limit) { const Batch batch = BuildHitsTable( "SELECT RegionID, SUM(AdvEngineID), COUNT(*) AS c, AVG(ResolutionWidth), COUNT(DISTINCT UserID) "