From 7f46b6a48b0b987e12f393558ee29359b82b9c02 Mon Sep 17 00:00:00 2001 From: Daniil Rubin Date: Mon, 8 Aug 2022 14:10:50 +0000 Subject: [PATCH 001/266] Add Hudi engine template --- src/Storages/StorageHudi.cpp | 35 +++++++++++++++++++++++++++++++ src/Storages/StorageHudi.h | 21 +++++++++++++++++++ src/Storages/registerStorages.cpp | 3 +++ 3 files changed, 59 insertions(+) create mode 100644 src/Storages/StorageHudi.cpp create mode 100644 src/Storages/StorageHudi.h diff --git a/src/Storages/StorageHudi.cpp b/src/Storages/StorageHudi.cpp new file mode 100644 index 00000000000..9c7e5485f3c --- /dev/null +++ b/src/Storages/StorageHudi.cpp @@ -0,0 +1,35 @@ +#include +#include + +namespace DB { + +namespace ErrorCodes { + extern const int NUMBER_OF_ARGUMENTS_DOESNT_MATCH; +} + +StorageHudi::StorageHudi( + const StorageID & table_id_, + ColumnsDescription /*columns_description_*/, + ConstraintsDescription /*constraints_description_*/, + const String & /*comment*/ +) : IStorage(table_id_) {} + + + +void registerStorageHudi(StorageFactory & factory) +{ + factory.registerStorage("Hudi", [](const StorageFactory::Arguments & args) + { + if (!args.engine_args.empty()) + throw Exception( + ErrorCodes::NUMBER_OF_ARGUMENTS_DOESNT_MATCH, + "Engine {} doesn't support any arguments ({} given)", + args.engine_name, + args.engine_args.size()); + + return std::make_shared(args.table_id, args.columns, args.constraints, args.comment); + }); +} + +} + diff --git a/src/Storages/StorageHudi.h b/src/Storages/StorageHudi.h new file mode 100644 index 00000000000..e4fb52c6d39 --- /dev/null +++ b/src/Storages/StorageHudi.h @@ -0,0 +1,21 @@ +#pragma once + +#include "config_core.h" + +#include + +namespace DB +{ + +class StorageHudi final : public IStorage { +public: + StorageHudi( + const StorageID & table_id_, + ColumnsDescription columns_description_, + ConstraintsDescription constraints_description_, + const String & comment); + + String getName() const override { return "Hudi"; } +}; + +} diff --git a/src/Storages/registerStorages.cpp b/src/Storages/registerStorages.cpp index 575b3de7ae2..b5561243e56 100644 --- a/src/Storages/registerStorages.cpp +++ b/src/Storages/registerStorages.cpp @@ -88,6 +88,7 @@ void registerStorageFileLog(StorageFactory & factory); void registerStorageSQLite(StorageFactory & factory); #endif +void registerStorageHudi(StorageFactory & factory); void registerStorages() { @@ -171,6 +172,8 @@ void registerStorages() #if USE_SQLITE registerStorageSQLite(factory); #endif + + registerStorageHudi(factory); } } From c5ae7126517d7326ad02981a97c9989be571dd99 Mon Sep 17 00:00:00 2001 From: Daniil Rubin Date: Mon, 22 Aug 2022 09:37:20 +0000 Subject: [PATCH 002/266] Add list files on s3 --- src/Storages/StorageHudi.cpp | 136 ++++++++++++++++++++++++++++++++--- src/Storages/StorageHudi.h | 24 ++++++- 2 files changed, 150 insertions(+), 10 deletions(-) diff --git a/src/Storages/StorageHudi.cpp b/src/Storages/StorageHudi.cpp index 9c7e5485f3c..8c920808e5a 100644 --- a/src/Storages/StorageHudi.cpp +++ b/src/Storages/StorageHudi.cpp @@ -1,18 +1,122 @@ + +#include + #include #include +#include + +#include +#include +#include +#include +#include + namespace DB { namespace ErrorCodes { extern const int NUMBER_OF_ARGUMENTS_DOESNT_MATCH; + extern const int S3_ERROR; } StorageHudi::StorageHudi( + const S3::URI & uri_, + const String & access_key_, + const String & secret_access_key, const StorageID & table_id_, ColumnsDescription /*columns_description_*/, ConstraintsDescription /*constraints_description_*/, - const String & /*comment*/ -) : IStorage(table_id_) {} + const String & /*comment*/, + ContextPtr context_ +) : IStorage(table_id_) + , s3_configuration({uri_, access_key_, secret_access_key, {}, {}, {}}) + , log(&Poco::Logger::get("StorageHudi (" + table_id_.table_name + ")")) +{ + context_->getGlobalContext()->getRemoteHostFilter().checkURL(uri_.uri); + updateS3Configuration(context_, s3_configuration); + + const auto & client = s3_configuration.client; + { + Aws::S3::Model::ListObjectsV2Request request; + Aws::S3::Model::ListObjectsV2Outcome outcome; + + const auto key = "hudi"; + bool is_finished{false}; + const auto bucket{s3_configuration.uri.bucket}; + + request.SetBucket(bucket); + //request.SetPrefix(key); + + while (!is_finished) + { + outcome = client->ListObjectsV2(request); + if (!outcome.IsSuccess()) + throw Exception( + ErrorCodes::S3_ERROR, + "Could not list objects in bucket {} with key {}, S3 exception: {}, message: {}", + quoteString(bucket), + quoteString(key), + backQuote(outcome.GetError().GetExceptionName()), + quoteString(outcome.GetError().GetMessage())); + + const auto & result_batch = outcome.GetResult().GetContents(); + for (const auto & obj : result_batch) + { + const auto& filename = obj.GetKey(); + LOG_DEBUG(log, "Found file: {}", filename); + } + + request.SetContinuationToken(outcome.GetResult().GetNextContinuationToken()); + is_finished = !outcome.GetResult().GetIsTruncated(); + } + } + +} + +void StorageHudi::updateS3Configuration(ContextPtr ctx, StorageS3::S3Configuration & upd) +{ + auto settings = ctx->getStorageS3Settings().getSettings(upd.uri.uri.toString()); + + bool need_update_configuration = settings != S3Settings{}; + if (need_update_configuration) + { + if (upd.rw_settings != settings.rw_settings) + upd.rw_settings = settings.rw_settings; + } + + upd.rw_settings.updateFromSettingsIfEmpty(ctx->getSettings()); + + if (upd.client && (!upd.access_key_id.empty() || settings.auth_settings == upd.auth_settings)) + return; + + Aws::Auth::AWSCredentials credentials(upd.access_key_id, upd.secret_access_key); + HeaderCollection headers; + if (upd.access_key_id.empty()) + { + credentials = Aws::Auth::AWSCredentials(settings.auth_settings.access_key_id, settings.auth_settings.secret_access_key); + headers = settings.auth_settings.headers; + } + + S3::PocoHTTPClientConfiguration client_configuration = S3::ClientFactory::instance().createClientConfiguration( + settings.auth_settings.region, + ctx->getRemoteHostFilter(), ctx->getGlobalContext()->getSettingsRef().s3_max_redirects, + ctx->getGlobalContext()->getSettingsRef().enable_s3_requests_logging); + + client_configuration.endpointOverride = upd.uri.endpoint; + client_configuration.maxConnections = upd.rw_settings.max_connections; + + upd.client = S3::ClientFactory::instance().create( + client_configuration, + upd.uri.is_virtual_hosted_style, + credentials.GetAWSAccessKeyId(), + credentials.GetAWSSecretKey(), + settings.auth_settings.server_side_encryption_customer_key_base64, + std::move(headers), + settings.auth_settings.use_environment_credentials.value_or(ctx->getConfigRef().getBool("s3.use_environment_credentials", false)), + settings.auth_settings.use_insecure_imds_request.value_or(ctx->getConfigRef().getBool("s3.use_insecure_imds_request", false))); + + upd.auth_settings = std::move(settings.auth_settings); +} @@ -20,14 +124,28 @@ void registerStorageHudi(StorageFactory & factory) { factory.registerStorage("Hudi", [](const StorageFactory::Arguments & args) { - if (!args.engine_args.empty()) - throw Exception( - ErrorCodes::NUMBER_OF_ARGUMENTS_DOESNT_MATCH, - "Engine {} doesn't support any arguments ({} given)", - args.engine_name, - args.engine_args.size()); + auto & engine_args = args.engine_args; + if (engine_args.empty()) + throw Exception(ErrorCodes::BAD_ARGUMENTS, "External data source must have arguments"); - return std::make_shared(args.table_id, args.columns, args.constraints, args.comment); + auto configuration = StorageS3::getConfiguration(engine_args, args.getLocalContext()); + + configuration.url = checkAndGetLiteralArgument(engine_args[0], "url"); + configuration.auth_settings.access_key_id = checkAndGetLiteralArgument(engine_args[1], "access_key_id"); + configuration.auth_settings.secret_access_key = checkAndGetLiteralArgument(engine_args[2], "secret_access_key"); + + + S3::URI s3_uri(Poco::URI(configuration.url)); + + return std::make_shared( + s3_uri, + configuration.auth_settings.access_key_id, + configuration.auth_settings.secret_access_key, + args.table_id, + args.columns, + args.constraints, + args.comment, + args.getContext()); }); } diff --git a/src/Storages/StorageHudi.h b/src/Storages/StorageHudi.h index e4fb52c6d39..d64214d6c4b 100644 --- a/src/Storages/StorageHudi.h +++ b/src/Storages/StorageHudi.h @@ -3,6 +3,16 @@ #include "config_core.h" #include +#include + +namespace Poco { + class Logger; +} + +namespace Aws::S3 +{ + class S3Client; +} namespace DB { @@ -10,12 +20,24 @@ namespace DB class StorageHudi final : public IStorage { public: StorageHudi( + const S3::URI& uri_, + const String& access_key_, + const String& secret_access_key_, const StorageID & table_id_, ColumnsDescription columns_description_, ConstraintsDescription constraints_description_, - const String & comment); + const String & comment, + ContextPtr context_ + ); String getName() const override { return "Hudi"; } + +private: + static void updateS3Configuration(ContextPtr, StorageS3::S3Configuration &); + +private: + StorageS3::S3Configuration s3_configuration; + Poco::Logger * log; }; } From 842c636354e2abba87cd6d3b89db4718365ab780 Mon Sep 17 00:00:00 2001 From: Daniil Rubin Date: Wed, 24 Aug 2022 15:07:37 +0000 Subject: [PATCH 003/266] Undone hudi --- src/Storages/StorageHudi.cpp | 188 +++++++++++++++++++++++++++-------- src/Storages/StorageHudi.h | 19 +++- 2 files changed, 166 insertions(+), 41 deletions(-) diff --git a/src/Storages/StorageHudi.cpp b/src/Storages/StorageHudi.cpp index 8c920808e5a..afad299241c 100644 --- a/src/Storages/StorageHudi.cpp +++ b/src/Storages/StorageHudi.cpp @@ -22,57 +22,75 @@ namespace ErrorCodes { StorageHudi::StorageHudi( const S3::URI & uri_, const String & access_key_, - const String & secret_access_key, + const String & secret_access_key_, const StorageID & table_id_, - ColumnsDescription /*columns_description_*/, - ConstraintsDescription /*constraints_description_*/, - const String & /*comment*/, + const ColumnsDescription & columns_, + const ConstraintsDescription & constraints_, + const String & comment, ContextPtr context_ ) : IStorage(table_id_) - , s3_configuration({uri_, access_key_, secret_access_key, {}, {}, {}}) + , s3_configuration({uri_, access_key_, secret_access_key_, {}, {}, {}}) , log(&Poco::Logger::get("StorageHudi (" + table_id_.table_name + ")")) + , query("") + , engine(nullptr) { context_->getGlobalContext()->getRemoteHostFilter().checkURL(uri_.uri); + StorageInMemoryMetadata storage_metadata; + updateS3Configuration(context_, s3_configuration); - - const auto & client = s3_configuration.client; + + if (columns_.empty()) { - Aws::S3::Model::ListObjectsV2Request request; - Aws::S3::Model::ListObjectsV2Outcome outcome; - - const auto key = "hudi"; - bool is_finished{false}; - const auto bucket{s3_configuration.uri.bucket}; - - request.SetBucket(bucket); - //request.SetPrefix(key); - - while (!is_finished) - { - outcome = client->ListObjectsV2(request); - if (!outcome.IsSuccess()) - throw Exception( - ErrorCodes::S3_ERROR, - "Could not list objects in bucket {} with key {}, S3 exception: {}, message: {}", - quoteString(bucket), - quoteString(key), - backQuote(outcome.GetError().GetExceptionName()), - quoteString(outcome.GetError().GetMessage())); - - const auto & result_batch = outcome.GetResult().GetContents(); - for (const auto & obj : result_batch) - { - const auto& filename = obj.GetKey(); - LOG_DEBUG(log, "Found file: {}", filename); - } - - request.SetContinuationToken(outcome.GetResult().GetNextContinuationToken()); - is_finished = !outcome.GetResult().GetIsTruncated(); - } + auto columns = StorageS3::getTableStructureFromData( + "Parquet", + uri_, + access_key_, + secret_access_key_, + "", + false, + std::nullopt, + context_ + ); + storage_metadata.setColumns(columns); + } + else + { + storage_metadata.setColumns(columns_); } + storage_metadata.setConstraints(constraints_); + storage_metadata.setComment(comment); + setInMemoryMetadata(storage_metadata); } +Pipe StorageHudi::read( + const Names & column_names, + const StorageSnapshotPtr & storage_snapshot, + SelectQueryInfo & query_info, + ContextPtr context, + QueryProcessingStage::Enum processed_stage, + size_t max_block_size, + unsigned num_streams) +{ + auto keys = getKeysFromS3(); + + auto new_query = generateQueryFromKeys(std::forward(keys)); + // construct new engine + if (new_query != query) { + query = new_query; + auto new_query_uri = s3_configuration.uri.toString() + "/" + query; + engine = std::make_shared( + S3::URI(Poco::URI(new_query_uri)), + + ); + } + + return engine->read(column_names, storage_snapshot, + query_info, context, processed_stage, + max_block_size, num_streams) + + } + void StorageHudi::updateS3Configuration(ContextPtr ctx, StorageS3::S3Configuration & upd) { auto settings = ctx->getStorageS3Settings().getSettings(upd.uri.uri.toString()); @@ -118,6 +136,98 @@ void StorageHudi::updateS3Configuration(ContextPtr ctx, StorageS3::S3Configurati upd.auth_settings = std::move(settings.auth_settings); } +std::vector StorageHudi::getKeysFromS3() { + std::vector keys; + + const auto & client = s3_configuration.client; + + Aws::S3::Model::ListObjectsV2Request request; + Aws::S3::Model::ListObjectsV2Outcome outcome; + + bool is_finished{false}; + const auto bucket{s3_configuration.uri.bucket}; + const std::string key = ""; + + request.SetBucket(bucket); + + while (!is_finished) + { + outcome = client->ListObjectsV2(request); + if (!outcome.IsSuccess()) + throw Exception( + ErrorCodes::S3_ERROR, + "Could not list objects in bucket {} with key {}, S3 exception: {}, message: {}", + quoteString(bucket), + quoteString(key), + backQuote(outcome.GetError().GetExceptionName()), + quoteString(outcome.GetError().GetMessage())); + + const auto & result_batch = outcome.GetResult().GetContents(); + for (const auto & obj : result_batch) + { + const auto& filename = obj.GetKey(); + keys.push_back(filename); + LOG_DEBUG(log, "Found file: {}", filename); + } + + request.SetContinuationToken(outcome.GetResult().GetNextContinuationToken()); + is_finished = !outcome.GetResult().GetIsTruncated(); + } + + return keys; +} + +std::string StorageHudi::generateQueryFromKeys(std::vector&& keys) { + // filter only .parquet files + std::erase_if(keys, [](const std::string& s) { + if (s.size() >= 8) { + return s.substr(s.size() - 8) != ".parquet"; + } + return true; + }); + + // for each partition path take only latest parquet file + + std::unordered_map> latest_parquets; + + for (const auto& key : keys) { + + auto slash = key.find_last_of("/"); + std::string path; + if (slash == std::string::npos) { + path = ""; + } else { + path = key.substr(0, slash); + } + + uint64_t timestamp = std::stoul(key.substr(key.find_last_of("_")+1)); + + auto it = latest_parquets.find(path); + + if (it != latest_parquets.end()) { + if (it->second.second < timestamp) { + it->second = {key, timestamp}; + } + } else { + latest_parquets[path] = {key, timestamp}; + } + } + + std::vector filtered_keys; + std::transform(latest_parquets.begin(), latest_parquets.end(), std::back_inserter(filtered_keys), [](const auto& kv){return kv.second.first;}); + + std::string new_query; + + for (auto&& key : filtered_keys) { + if (!new_query.empty()) { + new_query += ","; + } + new_query += key; + } + new_query = "{" + new_query + "}"; + + return new_query; +} void registerStorageHudi(StorageFactory & factory) diff --git a/src/Storages/StorageHudi.h b/src/Storages/StorageHudi.h index d64214d6c4b..b02de7bf130 100644 --- a/src/Storages/StorageHudi.h +++ b/src/Storages/StorageHudi.h @@ -24,20 +24,35 @@ public: const String& access_key_, const String& secret_access_key_, const StorageID & table_id_, - ColumnsDescription columns_description_, - ConstraintsDescription constraints_description_, + const ColumnsDescription & columns_, + const ConstraintsDescription & constraints_, const String & comment, ContextPtr context_ ); String getName() const override { return "Hudi"; } + Pipe read( + const Names & column_names, + const StorageSnapshotPtr & storage_snapshot, + SelectQueryInfo & query_info, + ContextPtr context, + QueryProcessingStage::Enum processed_stage, + size_t max_block_size, + unsigned num_streams) override; + private: static void updateS3Configuration(ContextPtr, StorageS3::S3Configuration &); +private: + std::vector getKeysFromS3(); + std::string generateQueryFromKeys(std::vector&& keys); + private: StorageS3::S3Configuration s3_configuration; Poco::Logger * log; + std::string query; + std::shared_ptr engine; }; } From 66c9305668f9ce515a86be04cd01cf376c80da73 Mon Sep 17 00:00:00 2001 From: Daniil Rubin Date: Thu, 25 Aug 2022 08:48:49 +0000 Subject: [PATCH 004/266] Add Hudi engine --- src/Storages/StorageHudi.cpp | 84 +++++++++++++++++++++--------------- src/Storages/StorageHudi.h | 7 ++- 2 files changed, 53 insertions(+), 38 deletions(-) diff --git a/src/Storages/StorageHudi.cpp b/src/Storages/StorageHudi.cpp index afad299241c..21086a7e085 100644 --- a/src/Storages/StorageHudi.cpp +++ b/src/Storages/StorageHudi.cpp @@ -1,16 +1,16 @@ - +#include +#include #include -#include +#include #include #include #include #include #include -#include -#include +#include namespace DB { @@ -29,38 +29,55 @@ StorageHudi::StorageHudi( const String & comment, ContextPtr context_ ) : IStorage(table_id_) - , s3_configuration({uri_, access_key_, secret_access_key_, {}, {}, {}}) + , base_configuration({uri_, access_key_, secret_access_key_, {}, {}, {}}) , log(&Poco::Logger::get("StorageHudi (" + table_id_.table_name + ")")) - , query("") - , engine(nullptr) { - context_->getGlobalContext()->getRemoteHostFilter().checkURL(uri_.uri); StorageInMemoryMetadata storage_metadata; + updateS3Configuration(context_, base_configuration); - updateS3Configuration(context_, s3_configuration); + auto keys = getKeysFromS3(); + + auto new_uri = base_configuration.uri.uri.toString() + generateQueryFromKeys(std::move(keys)); + LOG_DEBUG(log, "New uri: {}", new_uri); + + auto s3_uri = S3::URI(Poco::URI(new_uri)); +// StorageS3::S3Configuration s3_configuration{s3_uri, access_key_, secret_access_key_, {}, {}, {}}; + if (columns_.empty()) { auto columns = StorageS3::getTableStructureFromData( - "Parquet", - uri_, + String("Parquet"), + s3_uri, access_key_, secret_access_key_, "", false, - std::nullopt, + {}, context_ - ); + ); storage_metadata.setColumns(columns); } - else - { + else storage_metadata.setColumns(columns_); - } storage_metadata.setConstraints(constraints_); storage_metadata.setComment(comment); setInMemoryMetadata(storage_metadata); + + s3engine = std::make_shared( + s3_uri, + access_key_, + secret_access_key_, + table_id_, + String("Parquet"), // format name + base_configuration.rw_settings, + columns_, + constraints_, + comment, + context_, + std::nullopt + ); } Pipe StorageHudi::read( @@ -72,24 +89,18 @@ Pipe StorageHudi::read( size_t max_block_size, unsigned num_streams) { - auto keys = getKeysFromS3(); + updateS3Configuration(context, base_configuration); - auto new_query = generateQueryFromKeys(std::forward(keys)); - // construct new engine - if (new_query != query) { - query = new_query; - auto new_query_uri = s3_configuration.uri.toString() + "/" + query; - engine = std::make_shared( - S3::URI(Poco::URI(new_query_uri)), - - ); - } + //auto keys = getKeysFromS3(); - return engine->read(column_names, storage_snapshot, + //auto new_uri = base_configuration.uri.uri.toString() + "/" + generateQueryFromKeys(std::forward(keys)); + //s3_configuration.uri = S3::URI(Poco::URI(new_uri)); + + return s3engine->read(column_names, storage_snapshot, query_info, context, processed_stage, - max_block_size, num_streams) + max_block_size, num_streams); - } +} void StorageHudi::updateS3Configuration(ContextPtr ctx, StorageS3::S3Configuration & upd) { @@ -139,13 +150,13 @@ void StorageHudi::updateS3Configuration(ContextPtr ctx, StorageS3::S3Configurati std::vector StorageHudi::getKeysFromS3() { std::vector keys; - const auto & client = s3_configuration.client; + const auto & client = base_configuration.client; Aws::S3::Model::ListObjectsV2Request request; Aws::S3::Model::ListObjectsV2Outcome outcome; bool is_finished{false}; - const auto bucket{s3_configuration.uri.bucket}; + const auto bucket{base_configuration.uri.bucket}; const std::string key = ""; request.SetBucket(bucket); @@ -167,7 +178,7 @@ std::vector StorageHudi::getKeysFromS3() { { const auto& filename = obj.GetKey(); keys.push_back(filename); - LOG_DEBUG(log, "Found file: {}", filename); + //LOG_DEBUG(log, "Found file: {}", filename); } request.SetContinuationToken(outcome.GetResult().GetNextContinuationToken()); @@ -256,7 +267,12 @@ void registerStorageHudi(StorageFactory & factory) args.constraints, args.comment, args.getContext()); - }); + }, + { + .supports_settings = true, + .supports_schema_inference = true, + .source_access_type = AccessType::S3, + }); } } diff --git a/src/Storages/StorageHudi.h b/src/Storages/StorageHudi.h index b02de7bf130..61e2af1340d 100644 --- a/src/Storages/StorageHudi.h +++ b/src/Storages/StorageHudi.h @@ -17,7 +17,7 @@ namespace Aws::S3 namespace DB { -class StorageHudi final : public IStorage { +class StorageHudi : public IStorage { public: StorageHudi( const S3::URI& uri_, @@ -49,10 +49,9 @@ private: std::string generateQueryFromKeys(std::vector&& keys); private: - StorageS3::S3Configuration s3_configuration; + StorageS3::S3Configuration base_configuration; + std::shared_ptr s3engine; Poco::Logger * log; - std::string query; - std::shared_ptr engine; }; } From e82c4800f282bb750cbd3fbec066d264c63b7d4a Mon Sep 17 00:00:00 2001 From: Daniil Rubin Date: Fri, 26 Aug 2022 18:17:32 +0000 Subject: [PATCH 005/266] Better --- src/Storages/StorageHudi.cpp | 134 ++++++++++++++++++----------------- src/Storages/StorageHudi.h | 23 +++--- 2 files changed, 80 insertions(+), 77 deletions(-) diff --git a/src/Storages/StorageHudi.cpp b/src/Storages/StorageHudi.cpp index 21086a7e085..efba2d3f85f 100644 --- a/src/Storages/StorageHudi.cpp +++ b/src/Storages/StorageHudi.cpp @@ -12,9 +12,11 @@ #include -namespace DB { +namespace DB +{ -namespace ErrorCodes { +namespace ErrorCodes +{ extern const int NUMBER_OF_ARGUMENTS_DOESNT_MATCH; extern const int S3_ERROR; } @@ -24,11 +26,11 @@ StorageHudi::StorageHudi( const String & access_key_, const String & secret_access_key_, const StorageID & table_id_, - const ColumnsDescription & columns_, + ColumnsDescription columns_, const ConstraintsDescription & constraints_, const String & comment, - ContextPtr context_ -) : IStorage(table_id_) + ContextPtr context_) + : IStorage(table_id_) , base_configuration({uri_, access_key_, secret_access_key_, {}, {}, {}}) , log(&Poco::Logger::get("StorageHudi (" + table_id_.table_name + ")")) { @@ -38,25 +40,16 @@ StorageHudi::StorageHudi( auto keys = getKeysFromS3(); auto new_uri = base_configuration.uri.uri.toString() + generateQueryFromKeys(std::move(keys)); - + LOG_DEBUG(log, "New uri: {}", new_uri); auto s3_uri = S3::URI(Poco::URI(new_uri)); -// StorageS3::S3Configuration s3_configuration{s3_uri, access_key_, secret_access_key_, {}, {}, {}}; if (columns_.empty()) { - auto columns = StorageS3::getTableStructureFromData( - String("Parquet"), - s3_uri, - access_key_, - secret_access_key_, - "", - false, - {}, - context_ - ); - storage_metadata.setColumns(columns); + columns_ + = StorageS3::getTableStructureFromData(String("Parquet"), s3_uri, access_key_, secret_access_key_, "", false, {}, context_); + storage_metadata.setColumns(columns_); } else storage_metadata.setColumns(columns_); @@ -76,30 +69,21 @@ StorageHudi::StorageHudi( constraints_, comment, context_, - std::nullopt - ); + std::nullopt); } Pipe StorageHudi::read( - const Names & column_names, - const StorageSnapshotPtr & storage_snapshot, - SelectQueryInfo & query_info, - ContextPtr context, - QueryProcessingStage::Enum processed_stage, - size_t max_block_size, - unsigned num_streams) + const Names & column_names, + const StorageSnapshotPtr & storage_snapshot, + SelectQueryInfo & query_info, + ContextPtr context, + QueryProcessingStage::Enum processed_stage, + size_t max_block_size, + unsigned num_streams) { updateS3Configuration(context, base_configuration); - //auto keys = getKeysFromS3(); - - //auto new_uri = base_configuration.uri.uri.toString() + "/" + generateQueryFromKeys(std::forward(keys)); - //s3_configuration.uri = S3::URI(Poco::URI(new_uri)); - - return s3engine->read(column_names, storage_snapshot, - query_info, context, processed_stage, - max_block_size, num_streams); - + return s3engine->read(column_names, storage_snapshot, query_info, context, processed_stage, max_block_size, num_streams); } void StorageHudi::updateS3Configuration(ContextPtr ctx, StorageS3::S3Configuration & upd) @@ -128,7 +112,8 @@ void StorageHudi::updateS3Configuration(ContextPtr ctx, StorageS3::S3Configurati S3::PocoHTTPClientConfiguration client_configuration = S3::ClientFactory::instance().createClientConfiguration( settings.auth_settings.region, - ctx->getRemoteHostFilter(), ctx->getGlobalContext()->getSettingsRef().s3_max_redirects, + ctx->getRemoteHostFilter(), + ctx->getGlobalContext()->getSettingsRef().s3_max_redirects, ctx->getGlobalContext()->getSettingsRef().enable_s3_requests_logging); client_configuration.endpointOverride = upd.uri.endpoint; @@ -147,11 +132,12 @@ void StorageHudi::updateS3Configuration(ContextPtr ctx, StorageS3::S3Configurati upd.auth_settings = std::move(settings.auth_settings); } -std::vector StorageHudi::getKeysFromS3() { +std::vector StorageHudi::getKeysFromS3() +{ std::vector keys; const auto & client = base_configuration.client; - + Aws::S3::Model::ListObjectsV2Request request; Aws::S3::Model::ListObjectsV2Outcome outcome; @@ -176,7 +162,7 @@ std::vector StorageHudi::getKeysFromS3() { const auto & result_batch = outcome.GetResult().GetContents(); for (const auto & obj : result_batch) { - const auto& filename = obj.GetKey(); + const auto & filename = obj.GetKey(); keys.push_back(filename); //LOG_DEBUG(log, "Found file: {}", filename); } @@ -188,49 +174,64 @@ std::vector StorageHudi::getKeysFromS3() { return keys; } -std::string StorageHudi::generateQueryFromKeys(std::vector&& keys) { +std::string StorageHudi::generateQueryFromKeys(std::vector && keys) +{ // filter only .parquet files - std::erase_if(keys, [](const std::string& s) { - if (s.size() >= 8) { - return s.substr(s.size() - 8) != ".parquet"; - } - return true; - }); + std::erase_if( + keys, + [](const std::string & s) + { + if (s.size() >= 8) + { + return s.substr(s.size() - 8) != ".parquet"; + } + return true; + }); // for each partition path take only latest parquet file std::unordered_map> latest_parquets; - - for (const auto& key : keys) { + for (const auto & key : keys) + { auto slash = key.find_last_of("/"); std::string path; - if (slash == std::string::npos) { + if (slash == std::string::npos) + { path = ""; - } else { + } + else + { path = key.substr(0, slash); } - uint64_t timestamp = std::stoul(key.substr(key.find_last_of("_")+1)); + uint64_t timestamp = std::stoul(key.substr(key.find_last_of("_") + 1)); auto it = latest_parquets.find(path); - - if (it != latest_parquets.end()) { - if (it->second.second < timestamp) { + + if (it != latest_parquets.end()) + { + if (it->second.second < timestamp) + { it->second = {key, timestamp}; } - } else { + } + else + { latest_parquets[path] = {key, timestamp}; } } std::vector filtered_keys; - std::transform(latest_parquets.begin(), latest_parquets.end(), std::back_inserter(filtered_keys), [](const auto& kv){return kv.second.first;}); + std::transform( + latest_parquets.begin(), latest_parquets.end(), std::back_inserter(filtered_keys), [](const auto & kv) { return kv.second.first; }); std::string new_query; - - for (auto&& key : filtered_keys) { - if (!new_query.empty()) { + + for (auto && key : filtered_keys) + { + if (!new_query.empty()) + { new_query += ","; } new_query += key; @@ -243,7 +244,9 @@ std::string StorageHudi::generateQueryFromKeys(std::vector&& keys) void registerStorageHudi(StorageFactory & factory) { - factory.registerStorage("Hudi", [](const StorageFactory::Arguments & args) + factory.registerStorage( + "Hudi", + [](const StorageFactory::Arguments & args) { auto & engine_args = args.engine_args; if (engine_args.empty()) @@ -269,11 +272,10 @@ void registerStorageHudi(StorageFactory & factory) args.getContext()); }, { - .supports_settings = true, - .supports_schema_inference = true, - .source_access_type = AccessType::S3, - }); + .supports_settings = true, + .supports_schema_inference = true, + .source_access_type = AccessType::S3, + }); } } - diff --git a/src/Storages/StorageHudi.h b/src/Storages/StorageHudi.h index 61e2af1340d..dd5cc18495e 100644 --- a/src/Storages/StorageHudi.h +++ b/src/Storages/StorageHudi.h @@ -5,30 +5,31 @@ #include #include -namespace Poco { - class Logger; +namespace Poco +{ +class Logger; } namespace Aws::S3 { - class S3Client; +class S3Client; } namespace DB { -class StorageHudi : public IStorage { +class StorageHudi : public IStorage +{ public: StorageHudi( - const S3::URI& uri_, - const String& access_key_, - const String& secret_access_key_, + const S3::URI & uri_, + const String & access_key_, + const String & secret_access_key_, const StorageID & table_id_, - const ColumnsDescription & columns_, + ColumnsDescription columns_, const ConstraintsDescription & constraints_, const String & comment, - ContextPtr context_ - ); + ContextPtr context_); String getName() const override { return "Hudi"; } @@ -46,7 +47,7 @@ private: private: std::vector getKeysFromS3(); - std::string generateQueryFromKeys(std::vector&& keys); + std::string generateQueryFromKeys(std::vector && keys); private: StorageS3::S3Configuration base_configuration; From 3e87fd0ee46236a159bab37412199ea927402db3 Mon Sep 17 00:00:00 2001 From: Daniil Rubin Date: Tue, 30 Aug 2022 09:14:05 +0000 Subject: [PATCH 006/266] Add test template --- .../integration/test_storage_hudi/__init__.py | 0 tests/integration/test_storage_hudi/test.py | 107 ++++++++++++ .../.hoodie/.20220830083647456.commit.crc | Bin 0 -> 60 bytes .../.20220830083647456.commit.requested.crc | Bin 0 -> 8 bytes .../.hoodie/.20220830083647456.inflight.crc | Bin 0 -> 44 bytes .../test_table/.hoodie/.hoodie.properties.crc | Bin 0 -> 16 bytes .../.hoodie/20220830083647456.commit | 165 ++++++++++++++++++ .../20220830083647456.commit.requested | 0 .../.hoodie/20220830083647456.inflight | 161 +++++++++++++++++ .../test_table/.hoodie/hoodie.properties | 21 +++ .../.hoodie/.00000000000000.deltacommit.crc | Bin 0 -> 72 bytes .../.00000000000000.deltacommit.inflight.crc | Bin 0 -> 32 bytes .../.00000000000000.deltacommit.requested.crc | Bin 0 -> 8 bytes .../.20220830083647456.deltacommit.crc | Bin 0 -> 76 bytes ...20220830083647456.deltacommit.inflight.crc | Bin 0 -> 32 bytes ...0220830083647456.deltacommit.requested.crc | Bin 0 -> 8 bytes .../metadata/.hoodie/.hoodie.properties.crc | Bin 0 -> 16 bytes .../.hoodie/00000000000000.deltacommit | 97 ++++++++++ .../00000000000000.deltacommit.inflight | 116 ++++++++++++ .../00000000000000.deltacommit.requested | 0 .../.hoodie/20220830083647456.deltacommit | 97 ++++++++++ .../20220830083647456.deltacommit.inflight | 116 ++++++++++++ .../20220830083647456.deltacommit.requested | 0 .../metadata/.hoodie/hoodie.properties | 14 ++ ....files-0000_00000000000000.log.1_0-0-0.crc | Bin 0 -> 12 bytes ...iles-0000_00000000000000.log.1_0-52-57.crc | Bin 0 -> 96 bytes ...iles-0000_00000000000000.log.2_0-83-93.crc | Bin 0 -> 96 bytes .../files/..hoodie_partition_metadata.crc | Bin 0 -> 12 bytes .../.files-0000_00000000000000.log.1_0-0-0 | Bin 0 -> 124 bytes .../.files-0000_00000000000000.log.1_0-52-57 | Bin 0 -> 10928 bytes .../.files-0000_00000000000000.log.2_0-83-93 | Bin 0 -> 11180 bytes .../metadata/files/.hoodie_partition_metadata | 4 + .../sao_paulo/..hoodie_partition_metadata.crc | Bin 0 -> 12 bytes ...ad-0_0-73-83_20220830083647456.parquet.crc | Bin 0 -> 3432 bytes .../sao_paulo/.hoodie_partition_metadata | 4 + ...aebbad-0_0-73-83_20220830083647456.parquet | Bin 0 -> 437831 bytes .../..hoodie_partition_metadata.crc | Bin 0 -> 12 bytes ...55-0_1-73-84_20220830083647456.parquet.crc | Bin 0 -> 3432 bytes .../san_francisco/.hoodie_partition_metadata | 4 + ...268e55-0_1-73-84_20220830083647456.parquet | Bin 0 -> 438186 bytes .../chennai/..hoodie_partition_metadata.crc | Bin 0 -> 12 bytes ...e1-0_2-73-85_20220830083647456.parquet.crc | Bin 0 -> 3428 bytes .../india/chennai/.hoodie_partition_metadata | 4 + ...e5d6e1-0_2-73-85_20220830083647456.parquet | Bin 0 -> 437623 bytes 44 files changed, 910 insertions(+) create mode 100644 tests/integration/test_storage_hudi/__init__.py create mode 100644 tests/integration/test_storage_hudi/test.py create mode 100644 tests/integration/test_storage_hudi/test_table/.hoodie/.20220830083647456.commit.crc create mode 100644 tests/integration/test_storage_hudi/test_table/.hoodie/.20220830083647456.commit.requested.crc create mode 100644 tests/integration/test_storage_hudi/test_table/.hoodie/.20220830083647456.inflight.crc create mode 100644 tests/integration/test_storage_hudi/test_table/.hoodie/.hoodie.properties.crc create mode 100644 tests/integration/test_storage_hudi/test_table/.hoodie/20220830083647456.commit create mode 100644 tests/integration/test_storage_hudi/test_table/.hoodie/20220830083647456.commit.requested create mode 100644 tests/integration/test_storage_hudi/test_table/.hoodie/20220830083647456.inflight create mode 100644 tests/integration/test_storage_hudi/test_table/.hoodie/hoodie.properties create mode 100644 tests/integration/test_storage_hudi/test_table/.hoodie/metadata/.hoodie/.00000000000000.deltacommit.crc create mode 100644 tests/integration/test_storage_hudi/test_table/.hoodie/metadata/.hoodie/.00000000000000.deltacommit.inflight.crc create mode 100644 tests/integration/test_storage_hudi/test_table/.hoodie/metadata/.hoodie/.00000000000000.deltacommit.requested.crc create mode 100644 tests/integration/test_storage_hudi/test_table/.hoodie/metadata/.hoodie/.20220830083647456.deltacommit.crc create mode 100644 tests/integration/test_storage_hudi/test_table/.hoodie/metadata/.hoodie/.20220830083647456.deltacommit.inflight.crc create mode 100644 tests/integration/test_storage_hudi/test_table/.hoodie/metadata/.hoodie/.20220830083647456.deltacommit.requested.crc create mode 100644 tests/integration/test_storage_hudi/test_table/.hoodie/metadata/.hoodie/.hoodie.properties.crc create mode 100644 tests/integration/test_storage_hudi/test_table/.hoodie/metadata/.hoodie/00000000000000.deltacommit create mode 100644 tests/integration/test_storage_hudi/test_table/.hoodie/metadata/.hoodie/00000000000000.deltacommit.inflight create mode 100644 tests/integration/test_storage_hudi/test_table/.hoodie/metadata/.hoodie/00000000000000.deltacommit.requested create mode 100644 tests/integration/test_storage_hudi/test_table/.hoodie/metadata/.hoodie/20220830083647456.deltacommit create mode 100644 tests/integration/test_storage_hudi/test_table/.hoodie/metadata/.hoodie/20220830083647456.deltacommit.inflight create mode 100644 tests/integration/test_storage_hudi/test_table/.hoodie/metadata/.hoodie/20220830083647456.deltacommit.requested create mode 100644 tests/integration/test_storage_hudi/test_table/.hoodie/metadata/.hoodie/hoodie.properties create mode 100644 tests/integration/test_storage_hudi/test_table/.hoodie/metadata/files/..files-0000_00000000000000.log.1_0-0-0.crc create mode 100644 tests/integration/test_storage_hudi/test_table/.hoodie/metadata/files/..files-0000_00000000000000.log.1_0-52-57.crc create mode 100644 tests/integration/test_storage_hudi/test_table/.hoodie/metadata/files/..files-0000_00000000000000.log.2_0-83-93.crc create mode 100644 tests/integration/test_storage_hudi/test_table/.hoodie/metadata/files/..hoodie_partition_metadata.crc create mode 100644 tests/integration/test_storage_hudi/test_table/.hoodie/metadata/files/.files-0000_00000000000000.log.1_0-0-0 create mode 100644 tests/integration/test_storage_hudi/test_table/.hoodie/metadata/files/.files-0000_00000000000000.log.1_0-52-57 create mode 100644 tests/integration/test_storage_hudi/test_table/.hoodie/metadata/files/.files-0000_00000000000000.log.2_0-83-93 create mode 100644 tests/integration/test_storage_hudi/test_table/.hoodie/metadata/files/.hoodie_partition_metadata create mode 100644 tests/integration/test_storage_hudi/test_table/americas/brazil/sao_paulo/..hoodie_partition_metadata.crc create mode 100644 tests/integration/test_storage_hudi/test_table/americas/brazil/sao_paulo/.8a9a08bb-8cbc-4ec9-a2d4-8a6cdcaebbad-0_0-73-83_20220830083647456.parquet.crc create mode 100644 tests/integration/test_storage_hudi/test_table/americas/brazil/sao_paulo/.hoodie_partition_metadata create mode 100644 tests/integration/test_storage_hudi/test_table/americas/brazil/sao_paulo/8a9a08bb-8cbc-4ec9-a2d4-8a6cdcaebbad-0_0-73-83_20220830083647456.parquet create mode 100644 tests/integration/test_storage_hudi/test_table/americas/united_states/san_francisco/..hoodie_partition_metadata.crc create mode 100644 tests/integration/test_storage_hudi/test_table/americas/united_states/san_francisco/.34b1b177-f0af-467b-9214-473ead268e55-0_1-73-84_20220830083647456.parquet.crc create mode 100644 tests/integration/test_storage_hudi/test_table/americas/united_states/san_francisco/.hoodie_partition_metadata create mode 100644 tests/integration/test_storage_hudi/test_table/americas/united_states/san_francisco/34b1b177-f0af-467b-9214-473ead268e55-0_1-73-84_20220830083647456.parquet create mode 100644 tests/integration/test_storage_hudi/test_table/asia/india/chennai/..hoodie_partition_metadata.crc create mode 100644 tests/integration/test_storage_hudi/test_table/asia/india/chennai/.92aa634e-d83f-4057-a385-ea3b22e5d6e1-0_2-73-85_20220830083647456.parquet.crc create mode 100644 tests/integration/test_storage_hudi/test_table/asia/india/chennai/.hoodie_partition_metadata create mode 100644 tests/integration/test_storage_hudi/test_table/asia/india/chennai/92aa634e-d83f-4057-a385-ea3b22e5d6e1-0_2-73-85_20220830083647456.parquet diff --git a/tests/integration/test_storage_hudi/__init__.py b/tests/integration/test_storage_hudi/__init__.py new file mode 100644 index 00000000000..e69de29bb2d diff --git a/tests/integration/test_storage_hudi/test.py b/tests/integration/test_storage_hudi/test.py new file mode 100644 index 00000000000..716ec15524e --- /dev/null +++ b/tests/integration/test_storage_hudi/test.py @@ -0,0 +1,107 @@ +import logging +import os + +import helpers.client +import pytest +from helpers.cluster import ClickHouseCluster + +SCRIPT_DIR = os.path.dirname(os.path.realpath(__file__)) + +def prepare_s3_bucket(started_cluster): + bucket_read_write_policy = { + "Version": "2012-10-17", + "Statement": [ + { + "Sid": "", + "Effect": "Allow", + "Principal": {"AWS": "*"}, + "Action": "s3:GetBucketLocation", + "Resource": "arn:aws:s3:::root", + }, + { + "Sid": "", + "Effect": "Allow", + "Principal": {"AWS": "*"}, + "Action": "s3:ListBucket", + "Resource": "arn:aws:s3:::root", + }, + { + "Sid": "", + "Effect": "Allow", + "Principal": {"AWS": "*"}, + "Action": "s3:GetObject", + "Resource": "arn:aws:s3:::root/*", + }, + { + "Sid": "", + "Effect": "Allow", + "Principal": {"AWS": "*"}, + "Action": "s3:PutObject", + "Resource": "arn:aws:s3:::root/*", + }, + ], + } + + minio_client = started_cluster.minio_client + minio_client.set_bucket_policy( + started_cluster.minio_bucket, json.dumps(bucket_read_write_policy) + ) + +def upload_test_table(started_cluster): + bucket = started_cluster.minio_bucket + + for address, dirs, files in os.walk(f"{SCRIPT_DIR}/test_table"): + for name in files: + started_cluster.minio_client.fput_object(bucket, os.path.join(SCRIPT_DIR, address, name), os.path.join(address, name)) + + for obj in list( + minio.list_objects( + bucket, + recursive=True, + ) + ): + logging.info(obj.name) + +@pytest.fixture(scope="module") +def started_cluster(): + try: + cluster = ClickHouseCluster(__file__) + cluster.add_instance( + "dummy", + with_minio=True + ) + + logging.info("Starting cluster...") + cluster.start() + + prepare_s3_bucket(cluster) + logging.info("S3 bucket created") + + upload_test_table(cluster) + logging.info("Test table uploaded") + + yield cluster + + finally: + cluster.shutdown() + +def run_query(instance, query, stdin=None, settings=None): + # type: (ClickHouseInstance, str, object, dict) -> str + + logging.info("Running query '{}'...".format(query)) + result = instance.query(query, stdin=stdin, settings=settings) + logging.info("Query finished") + + return result + + +def test_create_query(started_cluster): + instance = started_cluster.instances["dummy"] + bucket = started_cluster.minio_bucket + + create_query = f"""CREATE TABLE hudi ENGINE=Hudi('http://{started_cluster.minio_ip}:{started_cluster.minio_port}/{bucket}/hudi', 'minio', 'minio123')""" + + run_query(instance, create_query) + +def test_select_query(): + pass \ No newline at end of file diff --git a/tests/integration/test_storage_hudi/test_table/.hoodie/.20220830083647456.commit.crc b/tests/integration/test_storage_hudi/test_table/.hoodie/.20220830083647456.commit.crc new file mode 100644 index 0000000000000000000000000000000000000000..4bba97b9515ddcd9dd09f226e1cd81e89f1e9026 GIT binary patch literal 60 zcmV-C0K@-da$^7h00IEYSJ`4^`+RY$Y`hZ?T{ literal 0 HcmV?d00001 diff --git a/tests/integration/test_storage_hudi/test_table/.hoodie/.20220830083647456.commit.requested.crc b/tests/integration/test_storage_hudi/test_table/.hoodie/.20220830083647456.commit.requested.crc new file mode 100644 index 0000000000000000000000000000000000000000..3b7b044936a890cd8d651d349a752d819d71d22c GIT binary patch literal 8 PcmYc;N@ieSU}69O2$TUk literal 0 HcmV?d00001 diff --git a/tests/integration/test_storage_hudi/test_table/.hoodie/.20220830083647456.inflight.crc b/tests/integration/test_storage_hudi/test_table/.hoodie/.20220830083647456.inflight.crc new file mode 100644 index 0000000000000000000000000000000000000000..21984c840bc3f9329c55d1d5824515745f9d0468 GIT binary patch literal 44 zcmYc;N@ieSU}Cskw&P5j)zKi6c-hD;M%E!O)z;;Tsn#4f+2CBxmvH@r-orPu0BZCS AO#lD@ literal 0 HcmV?d00001 diff --git a/tests/integration/test_storage_hudi/test_table/.hoodie/.hoodie.properties.crc b/tests/integration/test_storage_hudi/test_table/.hoodie/.hoodie.properties.crc new file mode 100644 index 0000000000000000000000000000000000000000..f67f76b7101c0cf7b1aba9818ac05738d10a419c GIT binary patch literal 16 XcmYc;N@ieSU}6a3SsyCGs3rmc8^!|c literal 0 HcmV?d00001 diff --git a/tests/integration/test_storage_hudi/test_table/.hoodie/20220830083647456.commit b/tests/integration/test_storage_hudi/test_table/.hoodie/20220830083647456.commit new file mode 100644 index 00000000000..f8d6c248f49 --- /dev/null +++ b/tests/integration/test_storage_hudi/test_table/.hoodie/20220830083647456.commit @@ -0,0 +1,165 @@ +{ + "partitionToWriteStats" : { + "americas/brazil/sao_paulo" : [ { + "fileId" : "8a9a08bb-8cbc-4ec9-a2d4-8a6cdcaebbad-0", + "path" : "americas/brazil/sao_paulo/8a9a08bb-8cbc-4ec9-a2d4-8a6cdcaebbad-0_0-73-83_20220830083647456.parquet", + "prevCommit" : "null", + "numWrites" : 3, + "numDeletes" : 0, + "numUpdateWrites" : 0, + "numInserts" : 3, + "totalWriteBytes" : 437831, + "totalWriteErrors" : 0, + "tempPath" : null, + "partitionPath" : "americas/brazil/sao_paulo", + "totalLogRecords" : 0, + "totalLogFilesCompacted" : 0, + "totalLogSizeCompacted" : 0, + "totalUpdatedRecordsCompacted" : 0, + "totalLogBlocks" : 0, + "totalCorruptLogBlock" : 0, + "totalRollbackBlocks" : 0, + "fileSizeInBytes" : 437831, + "minEventTime" : null, + "maxEventTime" : null + } ], + "americas/united_states/san_francisco" : [ { + "fileId" : "34b1b177-f0af-467b-9214-473ead268e55-0", + "path" : "americas/united_states/san_francisco/34b1b177-f0af-467b-9214-473ead268e55-0_1-73-84_20220830083647456.parquet", + "prevCommit" : "null", + "numWrites" : 5, + "numDeletes" : 0, + "numUpdateWrites" : 0, + "numInserts" : 5, + "totalWriteBytes" : 438186, + "totalWriteErrors" : 0, + "tempPath" : null, + "partitionPath" : "americas/united_states/san_francisco", + "totalLogRecords" : 0, + "totalLogFilesCompacted" : 0, + "totalLogSizeCompacted" : 0, + "totalUpdatedRecordsCompacted" : 0, + "totalLogBlocks" : 0, + "totalCorruptLogBlock" : 0, + "totalRollbackBlocks" : 0, + "fileSizeInBytes" : 438186, + "minEventTime" : null, + "maxEventTime" : null + } ], + "asia/india/chennai" : [ { + "fileId" : "92aa634e-d83f-4057-a385-ea3b22e5d6e1-0", + "path" : "asia/india/chennai/92aa634e-d83f-4057-a385-ea3b22e5d6e1-0_2-73-85_20220830083647456.parquet", + "prevCommit" : "null", + "numWrites" : 2, + "numDeletes" : 0, + "numUpdateWrites" : 0, + "numInserts" : 2, + "totalWriteBytes" : 437623, + "totalWriteErrors" : 0, + "tempPath" : null, + "partitionPath" : "asia/india/chennai", + "totalLogRecords" : 0, + "totalLogFilesCompacted" : 0, + "totalLogSizeCompacted" : 0, + "totalUpdatedRecordsCompacted" : 0, + "totalLogBlocks" : 0, + "totalCorruptLogBlock" : 0, + "totalRollbackBlocks" : 0, + "fileSizeInBytes" : 437623, + "minEventTime" : null, + "maxEventTime" : null + } ] + }, + "compacted" : false, + "extraMetadata" : { + "schema" : "{\"type\":\"record\",\"name\":\"test_table_record\",\"namespace\":\"hoodie.test_table\",\"fields\":[{\"name\":\"begin_lat\",\"type\":[\"null\",\"double\"],\"default\":null},{\"name\":\"begin_lon\",\"type\":[\"null\",\"double\"],\"default\":null},{\"name\":\"driver\",\"type\":[\"null\",\"string\"],\"default\":null},{\"name\":\"end_lat\",\"type\":[\"null\",\"double\"],\"default\":null},{\"name\":\"end_lon\",\"type\":[\"null\",\"double\"],\"default\":null},{\"name\":\"fare\",\"type\":[\"null\",\"double\"],\"default\":null},{\"name\":\"partitionpath\",\"type\":[\"null\",\"string\"],\"default\":null},{\"name\":\"rider\",\"type\":[\"null\",\"string\"],\"default\":null},{\"name\":\"ts\",\"type\":[\"null\",\"long\"],\"default\":null},{\"name\":\"uuid\",\"type\":[\"null\",\"string\"],\"default\":null}]}" + }, + "operationType" : "UPSERT", + "writeStats" : [ { + "fileId" : "8a9a08bb-8cbc-4ec9-a2d4-8a6cdcaebbad-0", + "path" : "americas/brazil/sao_paulo/8a9a08bb-8cbc-4ec9-a2d4-8a6cdcaebbad-0_0-73-83_20220830083647456.parquet", + "prevCommit" : "null", + "numWrites" : 3, + "numDeletes" : 0, + "numUpdateWrites" : 0, + "numInserts" : 3, + "totalWriteBytes" : 437831, + "totalWriteErrors" : 0, + "tempPath" : null, + "partitionPath" : "americas/brazil/sao_paulo", + "totalLogRecords" : 0, + "totalLogFilesCompacted" : 0, + "totalLogSizeCompacted" : 0, + "totalUpdatedRecordsCompacted" : 0, + "totalLogBlocks" : 0, + "totalCorruptLogBlock" : 0, + "totalRollbackBlocks" : 0, + "fileSizeInBytes" : 437831, + "minEventTime" : null, + "maxEventTime" : null + }, { + "fileId" : "34b1b177-f0af-467b-9214-473ead268e55-0", + "path" : "americas/united_states/san_francisco/34b1b177-f0af-467b-9214-473ead268e55-0_1-73-84_20220830083647456.parquet", + "prevCommit" : "null", + "numWrites" : 5, + "numDeletes" : 0, + "numUpdateWrites" : 0, + "numInserts" : 5, + "totalWriteBytes" : 438186, + "totalWriteErrors" : 0, + "tempPath" : null, + "partitionPath" : "americas/united_states/san_francisco", + "totalLogRecords" : 0, + "totalLogFilesCompacted" : 0, + "totalLogSizeCompacted" : 0, + "totalUpdatedRecordsCompacted" : 0, + "totalLogBlocks" : 0, + "totalCorruptLogBlock" : 0, + "totalRollbackBlocks" : 0, + "fileSizeInBytes" : 438186, + "minEventTime" : null, + "maxEventTime" : null + }, { + "fileId" : "92aa634e-d83f-4057-a385-ea3b22e5d6e1-0", + "path" : "asia/india/chennai/92aa634e-d83f-4057-a385-ea3b22e5d6e1-0_2-73-85_20220830083647456.parquet", + "prevCommit" : "null", + "numWrites" : 2, + "numDeletes" : 0, + "numUpdateWrites" : 0, + "numInserts" : 2, + "totalWriteBytes" : 437623, + "totalWriteErrors" : 0, + "tempPath" : null, + "partitionPath" : "asia/india/chennai", + "totalLogRecords" : 0, + "totalLogFilesCompacted" : 0, + "totalLogSizeCompacted" : 0, + "totalUpdatedRecordsCompacted" : 0, + "totalLogBlocks" : 0, + "totalCorruptLogBlock" : 0, + "totalRollbackBlocks" : 0, + "fileSizeInBytes" : 437623, + "minEventTime" : null, + "maxEventTime" : null + } ], + "totalRecordsDeleted" : 0, + "totalLogRecordsCompacted" : 0, + "totalLogFilesCompacted" : 0, + "totalCompactedRecordsUpdated" : 0, + "totalLogFilesSize" : 0, + "totalScanTime" : 0, + "totalCreateTime" : 563, + "totalUpsertTime" : 0, + "minAndMaxEventTime" : { + "Optional.empty" : { + "val" : null, + "present" : false + } + }, + "writePartitionPaths" : [ "americas/brazil/sao_paulo", "americas/united_states/san_francisco", "asia/india/chennai" ], + "fileIdAndRelativePaths" : { + "92aa634e-d83f-4057-a385-ea3b22e5d6e1-0" : "asia/india/chennai/92aa634e-d83f-4057-a385-ea3b22e5d6e1-0_2-73-85_20220830083647456.parquet", + "34b1b177-f0af-467b-9214-473ead268e55-0" : "americas/united_states/san_francisco/34b1b177-f0af-467b-9214-473ead268e55-0_1-73-84_20220830083647456.parquet", + "8a9a08bb-8cbc-4ec9-a2d4-8a6cdcaebbad-0" : "americas/brazil/sao_paulo/8a9a08bb-8cbc-4ec9-a2d4-8a6cdcaebbad-0_0-73-83_20220830083647456.parquet" + } +} \ No newline at end of file diff --git a/tests/integration/test_storage_hudi/test_table/.hoodie/20220830083647456.commit.requested b/tests/integration/test_storage_hudi/test_table/.hoodie/20220830083647456.commit.requested new file mode 100644 index 00000000000..e69de29bb2d diff --git a/tests/integration/test_storage_hudi/test_table/.hoodie/20220830083647456.inflight b/tests/integration/test_storage_hudi/test_table/.hoodie/20220830083647456.inflight new file mode 100644 index 00000000000..f5ef5c92c2b --- /dev/null +++ b/tests/integration/test_storage_hudi/test_table/.hoodie/20220830083647456.inflight @@ -0,0 +1,161 @@ +{ + "partitionToWriteStats" : { + "americas/brazil/sao_paulo" : [ { + "fileId" : "", + "path" : null, + "prevCommit" : "null", + "numWrites" : 0, + "numDeletes" : 0, + "numUpdateWrites" : 0, + "numInserts" : 3, + "totalWriteBytes" : 0, + "totalWriteErrors" : 0, + "tempPath" : null, + "partitionPath" : null, + "totalLogRecords" : 0, + "totalLogFilesCompacted" : 0, + "totalLogSizeCompacted" : 0, + "totalUpdatedRecordsCompacted" : 0, + "totalLogBlocks" : 0, + "totalCorruptLogBlock" : 0, + "totalRollbackBlocks" : 0, + "fileSizeInBytes" : 0, + "minEventTime" : null, + "maxEventTime" : null + } ], + "americas/united_states/san_francisco" : [ { + "fileId" : "", + "path" : null, + "prevCommit" : "null", + "numWrites" : 0, + "numDeletes" : 0, + "numUpdateWrites" : 0, + "numInserts" : 5, + "totalWriteBytes" : 0, + "totalWriteErrors" : 0, + "tempPath" : null, + "partitionPath" : null, + "totalLogRecords" : 0, + "totalLogFilesCompacted" : 0, + "totalLogSizeCompacted" : 0, + "totalUpdatedRecordsCompacted" : 0, + "totalLogBlocks" : 0, + "totalCorruptLogBlock" : 0, + "totalRollbackBlocks" : 0, + "fileSizeInBytes" : 0, + "minEventTime" : null, + "maxEventTime" : null + } ], + "asia/india/chennai" : [ { + "fileId" : "", + "path" : null, + "prevCommit" : "null", + "numWrites" : 0, + "numDeletes" : 0, + "numUpdateWrites" : 0, + "numInserts" : 2, + "totalWriteBytes" : 0, + "totalWriteErrors" : 0, + "tempPath" : null, + "partitionPath" : null, + "totalLogRecords" : 0, + "totalLogFilesCompacted" : 0, + "totalLogSizeCompacted" : 0, + "totalUpdatedRecordsCompacted" : 0, + "totalLogBlocks" : 0, + "totalCorruptLogBlock" : 0, + "totalRollbackBlocks" : 0, + "fileSizeInBytes" : 0, + "minEventTime" : null, + "maxEventTime" : null + } ] + }, + "compacted" : false, + "extraMetadata" : { }, + "operationType" : "UPSERT", + "writeStats" : [ { + "fileId" : "", + "path" : null, + "prevCommit" : "null", + "numWrites" : 0, + "numDeletes" : 0, + "numUpdateWrites" : 0, + "numInserts" : 3, + "totalWriteBytes" : 0, + "totalWriteErrors" : 0, + "tempPath" : null, + "partitionPath" : null, + "totalLogRecords" : 0, + "totalLogFilesCompacted" : 0, + "totalLogSizeCompacted" : 0, + "totalUpdatedRecordsCompacted" : 0, + "totalLogBlocks" : 0, + "totalCorruptLogBlock" : 0, + "totalRollbackBlocks" : 0, + "fileSizeInBytes" : 0, + "minEventTime" : null, + "maxEventTime" : null + }, { + "fileId" : "", + "path" : null, + "prevCommit" : "null", + "numWrites" : 0, + "numDeletes" : 0, + "numUpdateWrites" : 0, + "numInserts" : 5, + "totalWriteBytes" : 0, + "totalWriteErrors" : 0, + "tempPath" : null, + "partitionPath" : null, + "totalLogRecords" : 0, + "totalLogFilesCompacted" : 0, + "totalLogSizeCompacted" : 0, + "totalUpdatedRecordsCompacted" : 0, + "totalLogBlocks" : 0, + "totalCorruptLogBlock" : 0, + "totalRollbackBlocks" : 0, + "fileSizeInBytes" : 0, + "minEventTime" : null, + "maxEventTime" : null + }, { + "fileId" : "", + "path" : null, + "prevCommit" : "null", + "numWrites" : 0, + "numDeletes" : 0, + "numUpdateWrites" : 0, + "numInserts" : 2, + "totalWriteBytes" : 0, + "totalWriteErrors" : 0, + "tempPath" : null, + "partitionPath" : null, + "totalLogRecords" : 0, + "totalLogFilesCompacted" : 0, + "totalLogSizeCompacted" : 0, + "totalUpdatedRecordsCompacted" : 0, + "totalLogBlocks" : 0, + "totalCorruptLogBlock" : 0, + "totalRollbackBlocks" : 0, + "fileSizeInBytes" : 0, + "minEventTime" : null, + "maxEventTime" : null + } ], + "totalRecordsDeleted" : 0, + "totalLogRecordsCompacted" : 0, + "totalLogFilesCompacted" : 0, + "totalCompactedRecordsUpdated" : 0, + "totalLogFilesSize" : 0, + "totalScanTime" : 0, + "totalCreateTime" : 0, + "totalUpsertTime" : 0, + "minAndMaxEventTime" : { + "Optional.empty" : { + "val" : null, + "present" : false + } + }, + "writePartitionPaths" : [ "americas/brazil/sao_paulo", "americas/united_states/san_francisco", "asia/india/chennai" ], + "fileIdAndRelativePaths" : { + "" : null + } +} \ No newline at end of file diff --git a/tests/integration/test_storage_hudi/test_table/.hoodie/hoodie.properties b/tests/integration/test_storage_hudi/test_table/.hoodie/hoodie.properties new file mode 100644 index 00000000000..9ae364baf33 --- /dev/null +++ b/tests/integration/test_storage_hudi/test_table/.hoodie/hoodie.properties @@ -0,0 +1,21 @@ +#Updated at 2022-08-30T08:36:49.089844Z +#Tue Aug 30 08:36:49 UTC 2022 +hoodie.table.type=COPY_ON_WRITE +hoodie.table.metadata.partitions=files +hoodie.table.precombine.field=ts +hoodie.table.partition.fields=partitionpath +hoodie.archivelog.folder=archived +hoodie.timeline.layout.version=1 +hoodie.table.checksum=2702201862 +hoodie.datasource.write.drop.partition.columns=false +hoodie.table.timeline.timezone=LOCAL +hoodie.table.recordkey.fields=uuid +hoodie.table.name=test_table +hoodie.partition.metafile.use.base.format=false +hoodie.datasource.write.hive_style_partitioning=false +hoodie.populate.meta.fields=true +hoodie.table.keygenerator.class=org.apache.hudi.keygen.SimpleKeyGenerator +hoodie.table.base.file.format=PARQUET +hoodie.database.name= +hoodie.datasource.write.partitionpath.urlencode=false +hoodie.table.version=5 diff --git a/tests/integration/test_storage_hudi/test_table/.hoodie/metadata/.hoodie/.00000000000000.deltacommit.crc b/tests/integration/test_storage_hudi/test_table/.hoodie/metadata/.hoodie/.00000000000000.deltacommit.crc new file mode 100644 index 0000000000000000000000000000000000000000..352b882ec5a697e6d4d0754866a1cdec0be1d3be GIT binary patch literal 72 zcmV-O0Jr~Ra$^7h00ID+B#3JXBN@*inEmG*kvdVpwWAbT>N@Qjh0Y0p<^OR6NMYYe e)p}4$Xm-%78APNu2!RHTD1CAabbD4@y#KTXRUT6S literal 0 HcmV?d00001 diff --git a/tests/integration/test_storage_hudi/test_table/.hoodie/metadata/.hoodie/.00000000000000.deltacommit.inflight.crc b/tests/integration/test_storage_hudi/test_table/.hoodie/metadata/.hoodie/.00000000000000.deltacommit.inflight.crc new file mode 100644 index 0000000000000000000000000000000000000000..b6b8f7fc1a3a439cc0c8cf1a3c06491fcf54223f GIT binary patch literal 32 ncmYc;N@ieSU}8AynOb;@FLqIaqtjZc*wA7%;rSPOY^;g^r9}$) literal 0 HcmV?d00001 diff --git a/tests/integration/test_storage_hudi/test_table/.hoodie/metadata/.hoodie/.00000000000000.deltacommit.requested.crc b/tests/integration/test_storage_hudi/test_table/.hoodie/metadata/.hoodie/.00000000000000.deltacommit.requested.crc new file mode 100644 index 0000000000000000000000000000000000000000..3b7b044936a890cd8d651d349a752d819d71d22c GIT binary patch literal 8 PcmYc;N@ieSU}69O2$TUk literal 0 HcmV?d00001 diff --git a/tests/integration/test_storage_hudi/test_table/.hoodie/metadata/.hoodie/.20220830083647456.deltacommit.crc b/tests/integration/test_storage_hudi/test_table/.hoodie/metadata/.hoodie/.20220830083647456.deltacommit.crc new file mode 100644 index 0000000000000000000000000000000000000000..54abc5e9b72e87753d458af67e967cccaa242c8e GIT binary patch literal 76 zcmV-S0JHyNa$^7h00IEQeq^6A>X*!Te*(sYWI5Hv06BTKu=gO04+eJ-1Ey1Rqt#G) ismNQFuvqj*Kj+@fF0)E(wf0GSx`G_6G3KKp3jiwT&ms^2 literal 0 HcmV?d00001 diff --git a/tests/integration/test_storage_hudi/test_table/.hoodie/metadata/.hoodie/.20220830083647456.deltacommit.inflight.crc b/tests/integration/test_storage_hudi/test_table/.hoodie/metadata/.hoodie/.20220830083647456.deltacommit.inflight.crc new file mode 100644 index 0000000000000000000000000000000000000000..ec7cb5faf561c70caf37bd118b025f9e36947c07 GIT binary patch literal 32 ocmYc;N@ieSU}8AynOf-f;LElIN2j$?v7yDY|IOy^v9T%w0J~ufc>n+a literal 0 HcmV?d00001 diff --git a/tests/integration/test_storage_hudi/test_table/.hoodie/metadata/.hoodie/.20220830083647456.deltacommit.requested.crc b/tests/integration/test_storage_hudi/test_table/.hoodie/metadata/.hoodie/.20220830083647456.deltacommit.requested.crc new file mode 100644 index 0000000000000000000000000000000000000000..3b7b044936a890cd8d651d349a752d819d71d22c GIT binary patch literal 8 PcmYc;N@ieSU}69O2$TUk literal 0 HcmV?d00001 diff --git a/tests/integration/test_storage_hudi/test_table/.hoodie/metadata/.hoodie/.hoodie.properties.crc b/tests/integration/test_storage_hudi/test_table/.hoodie/metadata/.hoodie/.hoodie.properties.crc new file mode 100644 index 0000000000000000000000000000000000000000..509ae4501ee2922036326030efe3e5c74d1db059 GIT binary patch literal 16 XcmYc;N@ieSU}EsgOFNnOa!LyTBu)jD literal 0 HcmV?d00001 diff --git a/tests/integration/test_storage_hudi/test_table/.hoodie/metadata/.hoodie/00000000000000.deltacommit b/tests/integration/test_storage_hudi/test_table/.hoodie/metadata/.hoodie/00000000000000.deltacommit new file mode 100644 index 00000000000..6d22af6dd2e --- /dev/null +++ b/tests/integration/test_storage_hudi/test_table/.hoodie/metadata/.hoodie/00000000000000.deltacommit @@ -0,0 +1,97 @@ +{ + "partitionToWriteStats" : { + "files" : [ { + "fileId" : "files-0000", + "path" : "files/.files-0000_00000000000000.log.1_0-52-57", + "prevCommit" : "00000000000000", + "numWrites" : 1, + "numDeletes" : 0, + "numUpdateWrites" : 1, + "numInserts" : 0, + "totalWriteBytes" : 10928, + "totalWriteErrors" : 0, + "tempPath" : null, + "partitionPath" : "files", + "totalLogRecords" : 0, + "totalLogFilesCompacted" : 0, + "totalLogSizeCompacted" : 0, + "totalUpdatedRecordsCompacted" : 0, + "totalLogBlocks" : 0, + "totalCorruptLogBlock" : 0, + "totalRollbackBlocks" : 0, + "fileSizeInBytes" : 10928, + "minEventTime" : null, + "maxEventTime" : null, + "logVersion" : 1, + "logOffset" : 0, + "baseFile" : "", + "logFiles" : [ ".files-0000_00000000000000.log.1_0-52-57" ], + "recordsStats" : { + "val" : null, + "present" : false + }, + "columnStats" : { + "val" : null, + "present" : false + } + } ] + }, + "compacted" : false, + "extraMetadata" : { + "schema" : "{\"type\":\"record\",\"name\":\"HoodieMetadataRecord\",\"namespace\":\"org.apache.hudi.avro.model\",\"doc\":\"A record saved within the Metadata Table\",\"fields\":[{\"name\":\"key\",\"type\":{\"type\":\"string\",\"avro.java.string\":\"String\"}},{\"name\":\"type\",\"type\":\"int\",\"doc\":\"Type of the metadata record\"},{\"name\":\"filesystemMetadata\",\"type\":[\"null\",{\"type\":\"map\",\"values\":{\"type\":\"record\",\"name\":\"HoodieMetadataFileInfo\",\"fields\":[{\"name\":\"size\",\"type\":\"long\",\"doc\":\"Size of the file\"},{\"name\":\"isDeleted\",\"type\":\"boolean\",\"doc\":\"True if this file has been deleted\"}]},\"avro.java.string\":\"String\"}],\"doc\":\"Contains information about partitions and files within the dataset\"},{\"name\":\"BloomFilterMetadata\",\"type\":[\"null\",{\"type\":\"record\",\"name\":\"HoodieMetadataBloomFilter\",\"doc\":\"Data file bloom filter details\",\"fields\":[{\"name\":\"type\",\"type\":{\"type\":\"string\",\"avro.java.string\":\"String\"},\"doc\":\"Bloom filter type code\"},{\"name\":\"timestamp\",\"type\":{\"type\":\"string\",\"avro.java.string\":\"String\"},\"doc\":\"Instant timestamp when this metadata was created/updated\"},{\"name\":\"bloomFilter\",\"type\":\"bytes\",\"doc\":\"Bloom filter binary byte array\"},{\"name\":\"isDeleted\",\"type\":\"boolean\",\"doc\":\"Bloom filter entry valid/deleted flag\"}]}],\"doc\":\"Metadata Index of bloom filters for all data files in the user table\",\"default\":null},{\"name\":\"ColumnStatsMetadata\",\"type\":[\"null\",{\"type\":\"record\",\"name\":\"HoodieMetadataColumnStats\",\"doc\":\"Data file column statistics\",\"fields\":[{\"name\":\"fileName\",\"type\":[\"null\",{\"type\":\"string\",\"avro.java.string\":\"String\"}],\"doc\":\"File name for which this column statistics applies\",\"default\":null},{\"name\":\"columnName\",\"type\":[\"null\",{\"type\":\"string\",\"avro.java.string\":\"String\"}],\"doc\":\"Column name for which this column statistics applies\",\"default\":null},{\"name\":\"minValue\",\"type\":[\"null\",{\"type\":\"record\",\"name\":\"BooleanWrapper\",\"doc\":\"A record wrapping boolean type to be able to be used it w/in Avro's Union\",\"fields\":[{\"name\":\"value\",\"type\":\"boolean\"}]},{\"type\":\"record\",\"name\":\"IntWrapper\",\"doc\":\"A record wrapping int type to be able to be used it w/in Avro's Union\",\"fields\":[{\"name\":\"value\",\"type\":\"int\"}]},{\"type\":\"record\",\"name\":\"LongWrapper\",\"doc\":\"A record wrapping long type to be able to be used it w/in Avro's Union\",\"fields\":[{\"name\":\"value\",\"type\":\"long\"}]},{\"type\":\"record\",\"name\":\"FloatWrapper\",\"doc\":\"A record wrapping float type to be able to be used it w/in Avro's Union\",\"fields\":[{\"name\":\"value\",\"type\":\"float\"}]},{\"type\":\"record\",\"name\":\"DoubleWrapper\",\"doc\":\"A record wrapping double type to be able to be used it w/in Avro's Union\",\"fields\":[{\"name\":\"value\",\"type\":\"double\"}]},{\"type\":\"record\",\"name\":\"BytesWrapper\",\"doc\":\"A record wrapping bytes type to be able to be used it w/in Avro's Union\",\"fields\":[{\"name\":\"value\",\"type\":\"bytes\"}]},{\"type\":\"record\",\"name\":\"StringWrapper\",\"doc\":\"A record wrapping string type to be able to be used it w/in Avro's Union\",\"fields\":[{\"name\":\"value\",\"type\":{\"type\":\"string\",\"avro.java.string\":\"String\"}}]},{\"type\":\"record\",\"name\":\"DateWrapper\",\"doc\":\"A record wrapping Date logical type to be able to be used it w/in Avro's Union\",\"fields\":[{\"name\":\"value\",\"type\":\"int\"}]},{\"type\":\"record\",\"name\":\"DecimalWrapper\",\"doc\":\"A record wrapping Decimal logical type to be able to be used it w/in Avro's Union\",\"fields\":[{\"name\":\"value\",\"type\":{\"type\":\"bytes\",\"logicalType\":\"decimal\",\"precision\":30,\"scale\":15}}]},{\"type\":\"record\",\"name\":\"TimeMicrosWrapper\",\"doc\":\"A record wrapping Time-micros logical type to be able to be used it w/in Avro's Union\",\"fields\":[{\"name\":\"value\",\"type\":{\"type\":\"long\",\"logicalType\":\"time-micros\"}}]},{\"type\":\"record\",\"name\":\"TimestampMicrosWrapper\",\"doc\":\"A record wrapping Timestamp-micros logical type to be able to be used it w/in Avro's Union\",\"fields\":[{\"name\":\"value\",\"type\":\"long\"}]}],\"doc\":\"Minimum value in the range. Based on user data table schema, we can convert this to appropriate type\",\"default\":null},{\"name\":\"maxValue\",\"type\":[\"null\",\"BooleanWrapper\",\"IntWrapper\",\"LongWrapper\",\"FloatWrapper\",\"DoubleWrapper\",\"BytesWrapper\",\"StringWrapper\",\"DateWrapper\",\"DecimalWrapper\",\"TimeMicrosWrapper\",\"TimestampMicrosWrapper\"],\"doc\":\"Maximum value in the range. Based on user data table schema, we can convert it to appropriate type\",\"default\":null},{\"name\":\"valueCount\",\"type\":[\"null\",\"long\"],\"doc\":\"Total count of values\",\"default\":null},{\"name\":\"nullCount\",\"type\":[\"null\",\"long\"],\"doc\":\"Total count of null values\",\"default\":null},{\"name\":\"totalSize\",\"type\":[\"null\",\"long\"],\"doc\":\"Total storage size on disk\",\"default\":null},{\"name\":\"totalUncompressedSize\",\"type\":[\"null\",\"long\"],\"doc\":\"Total uncompressed storage size on disk\",\"default\":null},{\"name\":\"isDeleted\",\"type\":\"boolean\",\"doc\":\"Column range entry valid/deleted flag\"}]}],\"doc\":\"Metadata Index of column statistics for all data files in the user table\",\"default\":null}]}" + }, + "operationType" : "UPSERT_PREPPED", + "writeStats" : [ { + "fileId" : "files-0000", + "path" : "files/.files-0000_00000000000000.log.1_0-52-57", + "prevCommit" : "00000000000000", + "numWrites" : 1, + "numDeletes" : 0, + "numUpdateWrites" : 1, + "numInserts" : 0, + "totalWriteBytes" : 10928, + "totalWriteErrors" : 0, + "tempPath" : null, + "partitionPath" : "files", + "totalLogRecords" : 0, + "totalLogFilesCompacted" : 0, + "totalLogSizeCompacted" : 0, + "totalUpdatedRecordsCompacted" : 0, + "totalLogBlocks" : 0, + "totalCorruptLogBlock" : 0, + "totalRollbackBlocks" : 0, + "fileSizeInBytes" : 10928, + "minEventTime" : null, + "maxEventTime" : null, + "logVersion" : 1, + "logOffset" : 0, + "baseFile" : "", + "logFiles" : [ ".files-0000_00000000000000.log.1_0-52-57" ], + "recordsStats" : { + "val" : null, + "present" : false + }, + "columnStats" : { + "val" : null, + "present" : false + } + } ], + "totalRecordsDeleted" : 0, + "totalLogRecordsCompacted" : 0, + "totalLogFilesCompacted" : 0, + "totalCompactedRecordsUpdated" : 0, + "totalLogFilesSize" : 0, + "totalScanTime" : 0, + "totalCreateTime" : 0, + "totalUpsertTime" : 67, + "minAndMaxEventTime" : { + "Optional.empty" : { + "val" : null, + "present" : false + } + }, + "writePartitionPaths" : [ "files" ], + "fileIdAndRelativePaths" : { + "files-0000" : "files/.files-0000_00000000000000.log.1_0-52-57" + } +} \ No newline at end of file diff --git a/tests/integration/test_storage_hudi/test_table/.hoodie/metadata/.hoodie/00000000000000.deltacommit.inflight b/tests/integration/test_storage_hudi/test_table/.hoodie/metadata/.hoodie/00000000000000.deltacommit.inflight new file mode 100644 index 00000000000..bb2542e0186 --- /dev/null +++ b/tests/integration/test_storage_hudi/test_table/.hoodie/metadata/.hoodie/00000000000000.deltacommit.inflight @@ -0,0 +1,116 @@ +{ + "partitionToWriteStats" : { + "files" : [ { + "fileId" : "", + "path" : null, + "prevCommit" : "null", + "numWrites" : 0, + "numDeletes" : 0, + "numUpdateWrites" : 0, + "numInserts" : 0, + "totalWriteBytes" : 0, + "totalWriteErrors" : 0, + "tempPath" : null, + "partitionPath" : null, + "totalLogRecords" : 0, + "totalLogFilesCompacted" : 0, + "totalLogSizeCompacted" : 0, + "totalUpdatedRecordsCompacted" : 0, + "totalLogBlocks" : 0, + "totalCorruptLogBlock" : 0, + "totalRollbackBlocks" : 0, + "fileSizeInBytes" : 0, + "minEventTime" : null, + "maxEventTime" : null + }, { + "fileId" : "files-0000", + "path" : null, + "prevCommit" : "00000000000000", + "numWrites" : 0, + "numDeletes" : 0, + "numUpdateWrites" : 1, + "numInserts" : 0, + "totalWriteBytes" : 0, + "totalWriteErrors" : 0, + "tempPath" : null, + "partitionPath" : null, + "totalLogRecords" : 0, + "totalLogFilesCompacted" : 0, + "totalLogSizeCompacted" : 0, + "totalUpdatedRecordsCompacted" : 0, + "totalLogBlocks" : 0, + "totalCorruptLogBlock" : 0, + "totalRollbackBlocks" : 0, + "fileSizeInBytes" : 0, + "minEventTime" : null, + "maxEventTime" : null + } ] + }, + "compacted" : false, + "extraMetadata" : { }, + "operationType" : "UPSERT_PREPPED", + "writeStats" : [ { + "fileId" : "", + "path" : null, + "prevCommit" : "null", + "numWrites" : 0, + "numDeletes" : 0, + "numUpdateWrites" : 0, + "numInserts" : 0, + "totalWriteBytes" : 0, + "totalWriteErrors" : 0, + "tempPath" : null, + "partitionPath" : null, + "totalLogRecords" : 0, + "totalLogFilesCompacted" : 0, + "totalLogSizeCompacted" : 0, + "totalUpdatedRecordsCompacted" : 0, + "totalLogBlocks" : 0, + "totalCorruptLogBlock" : 0, + "totalRollbackBlocks" : 0, + "fileSizeInBytes" : 0, + "minEventTime" : null, + "maxEventTime" : null + }, { + "fileId" : "files-0000", + "path" : null, + "prevCommit" : "00000000000000", + "numWrites" : 0, + "numDeletes" : 0, + "numUpdateWrites" : 1, + "numInserts" : 0, + "totalWriteBytes" : 0, + "totalWriteErrors" : 0, + "tempPath" : null, + "partitionPath" : null, + "totalLogRecords" : 0, + "totalLogFilesCompacted" : 0, + "totalLogSizeCompacted" : 0, + "totalUpdatedRecordsCompacted" : 0, + "totalLogBlocks" : 0, + "totalCorruptLogBlock" : 0, + "totalRollbackBlocks" : 0, + "fileSizeInBytes" : 0, + "minEventTime" : null, + "maxEventTime" : null + } ], + "totalRecordsDeleted" : 0, + "totalLogRecordsCompacted" : 0, + "totalLogFilesCompacted" : 0, + "totalCompactedRecordsUpdated" : 0, + "totalLogFilesSize" : 0, + "totalScanTime" : 0, + "totalCreateTime" : 0, + "totalUpsertTime" : 0, + "minAndMaxEventTime" : { + "Optional.empty" : { + "val" : null, + "present" : false + } + }, + "writePartitionPaths" : [ "files" ], + "fileIdAndRelativePaths" : { + "" : null, + "files-0000" : null + } +} \ No newline at end of file diff --git a/tests/integration/test_storage_hudi/test_table/.hoodie/metadata/.hoodie/00000000000000.deltacommit.requested b/tests/integration/test_storage_hudi/test_table/.hoodie/metadata/.hoodie/00000000000000.deltacommit.requested new file mode 100644 index 00000000000..e69de29bb2d diff --git a/tests/integration/test_storage_hudi/test_table/.hoodie/metadata/.hoodie/20220830083647456.deltacommit b/tests/integration/test_storage_hudi/test_table/.hoodie/metadata/.hoodie/20220830083647456.deltacommit new file mode 100644 index 00000000000..210201f7135 --- /dev/null +++ b/tests/integration/test_storage_hudi/test_table/.hoodie/metadata/.hoodie/20220830083647456.deltacommit @@ -0,0 +1,97 @@ +{ + "partitionToWriteStats" : { + "files" : [ { + "fileId" : "files-0000", + "path" : "files/.files-0000_00000000000000.log.2_0-83-93", + "prevCommit" : "00000000000000", + "numWrites" : 4, + "numDeletes" : 0, + "numUpdateWrites" : 4, + "numInserts" : 0, + "totalWriteBytes" : 11180, + "totalWriteErrors" : 0, + "tempPath" : null, + "partitionPath" : "files", + "totalLogRecords" : 0, + "totalLogFilesCompacted" : 0, + "totalLogSizeCompacted" : 0, + "totalUpdatedRecordsCompacted" : 0, + "totalLogBlocks" : 0, + "totalCorruptLogBlock" : 0, + "totalRollbackBlocks" : 0, + "fileSizeInBytes" : 11180, + "minEventTime" : null, + "maxEventTime" : null, + "logVersion" : 2, + "logOffset" : 0, + "baseFile" : "", + "logFiles" : [ ".files-0000_00000000000000.log.1_0-52-57", ".files-0000_00000000000000.log.1_0-0-0", ".files-0000_00000000000000.log.2_0-83-93" ], + "recordsStats" : { + "val" : null, + "present" : false + }, + "columnStats" : { + "val" : null, + "present" : false + } + } ] + }, + "compacted" : false, + "extraMetadata" : { + "schema" : "{\"type\":\"record\",\"name\":\"HoodieMetadataRecord\",\"namespace\":\"org.apache.hudi.avro.model\",\"doc\":\"A record saved within the Metadata Table\",\"fields\":[{\"name\":\"key\",\"type\":{\"type\":\"string\",\"avro.java.string\":\"String\"}},{\"name\":\"type\",\"type\":\"int\",\"doc\":\"Type of the metadata record\"},{\"name\":\"filesystemMetadata\",\"type\":[\"null\",{\"type\":\"map\",\"values\":{\"type\":\"record\",\"name\":\"HoodieMetadataFileInfo\",\"fields\":[{\"name\":\"size\",\"type\":\"long\",\"doc\":\"Size of the file\"},{\"name\":\"isDeleted\",\"type\":\"boolean\",\"doc\":\"True if this file has been deleted\"}]},\"avro.java.string\":\"String\"}],\"doc\":\"Contains information about partitions and files within the dataset\"},{\"name\":\"BloomFilterMetadata\",\"type\":[\"null\",{\"type\":\"record\",\"name\":\"HoodieMetadataBloomFilter\",\"doc\":\"Data file bloom filter details\",\"fields\":[{\"name\":\"type\",\"type\":{\"type\":\"string\",\"avro.java.string\":\"String\"},\"doc\":\"Bloom filter type code\"},{\"name\":\"timestamp\",\"type\":{\"type\":\"string\",\"avro.java.string\":\"String\"},\"doc\":\"Instant timestamp when this metadata was created/updated\"},{\"name\":\"bloomFilter\",\"type\":\"bytes\",\"doc\":\"Bloom filter binary byte array\"},{\"name\":\"isDeleted\",\"type\":\"boolean\",\"doc\":\"Bloom filter entry valid/deleted flag\"}]}],\"doc\":\"Metadata Index of bloom filters for all data files in the user table\",\"default\":null},{\"name\":\"ColumnStatsMetadata\",\"type\":[\"null\",{\"type\":\"record\",\"name\":\"HoodieMetadataColumnStats\",\"doc\":\"Data file column statistics\",\"fields\":[{\"name\":\"fileName\",\"type\":[\"null\",{\"type\":\"string\",\"avro.java.string\":\"String\"}],\"doc\":\"File name for which this column statistics applies\",\"default\":null},{\"name\":\"columnName\",\"type\":[\"null\",{\"type\":\"string\",\"avro.java.string\":\"String\"}],\"doc\":\"Column name for which this column statistics applies\",\"default\":null},{\"name\":\"minValue\",\"type\":[\"null\",{\"type\":\"record\",\"name\":\"BooleanWrapper\",\"doc\":\"A record wrapping boolean type to be able to be used it w/in Avro's Union\",\"fields\":[{\"name\":\"value\",\"type\":\"boolean\"}]},{\"type\":\"record\",\"name\":\"IntWrapper\",\"doc\":\"A record wrapping int type to be able to be used it w/in Avro's Union\",\"fields\":[{\"name\":\"value\",\"type\":\"int\"}]},{\"type\":\"record\",\"name\":\"LongWrapper\",\"doc\":\"A record wrapping long type to be able to be used it w/in Avro's Union\",\"fields\":[{\"name\":\"value\",\"type\":\"long\"}]},{\"type\":\"record\",\"name\":\"FloatWrapper\",\"doc\":\"A record wrapping float type to be able to be used it w/in Avro's Union\",\"fields\":[{\"name\":\"value\",\"type\":\"float\"}]},{\"type\":\"record\",\"name\":\"DoubleWrapper\",\"doc\":\"A record wrapping double type to be able to be used it w/in Avro's Union\",\"fields\":[{\"name\":\"value\",\"type\":\"double\"}]},{\"type\":\"record\",\"name\":\"BytesWrapper\",\"doc\":\"A record wrapping bytes type to be able to be used it w/in Avro's Union\",\"fields\":[{\"name\":\"value\",\"type\":\"bytes\"}]},{\"type\":\"record\",\"name\":\"StringWrapper\",\"doc\":\"A record wrapping string type to be able to be used it w/in Avro's Union\",\"fields\":[{\"name\":\"value\",\"type\":{\"type\":\"string\",\"avro.java.string\":\"String\"}}]},{\"type\":\"record\",\"name\":\"DateWrapper\",\"doc\":\"A record wrapping Date logical type to be able to be used it w/in Avro's Union\",\"fields\":[{\"name\":\"value\",\"type\":\"int\"}]},{\"type\":\"record\",\"name\":\"DecimalWrapper\",\"doc\":\"A record wrapping Decimal logical type to be able to be used it w/in Avro's Union\",\"fields\":[{\"name\":\"value\",\"type\":{\"type\":\"bytes\",\"logicalType\":\"decimal\",\"precision\":30,\"scale\":15}}]},{\"type\":\"record\",\"name\":\"TimeMicrosWrapper\",\"doc\":\"A record wrapping Time-micros logical type to be able to be used it w/in Avro's Union\",\"fields\":[{\"name\":\"value\",\"type\":{\"type\":\"long\",\"logicalType\":\"time-micros\"}}]},{\"type\":\"record\",\"name\":\"TimestampMicrosWrapper\",\"doc\":\"A record wrapping Timestamp-micros logical type to be able to be used it w/in Avro's Union\",\"fields\":[{\"name\":\"value\",\"type\":\"long\"}]}],\"doc\":\"Minimum value in the range. Based on user data table schema, we can convert this to appropriate type\",\"default\":null},{\"name\":\"maxValue\",\"type\":[\"null\",\"BooleanWrapper\",\"IntWrapper\",\"LongWrapper\",\"FloatWrapper\",\"DoubleWrapper\",\"BytesWrapper\",\"StringWrapper\",\"DateWrapper\",\"DecimalWrapper\",\"TimeMicrosWrapper\",\"TimestampMicrosWrapper\"],\"doc\":\"Maximum value in the range. Based on user data table schema, we can convert it to appropriate type\",\"default\":null},{\"name\":\"valueCount\",\"type\":[\"null\",\"long\"],\"doc\":\"Total count of values\",\"default\":null},{\"name\":\"nullCount\",\"type\":[\"null\",\"long\"],\"doc\":\"Total count of null values\",\"default\":null},{\"name\":\"totalSize\",\"type\":[\"null\",\"long\"],\"doc\":\"Total storage size on disk\",\"default\":null},{\"name\":\"totalUncompressedSize\",\"type\":[\"null\",\"long\"],\"doc\":\"Total uncompressed storage size on disk\",\"default\":null},{\"name\":\"isDeleted\",\"type\":\"boolean\",\"doc\":\"Column range entry valid/deleted flag\"}]}],\"doc\":\"Metadata Index of column statistics for all data files in the user table\",\"default\":null}]}" + }, + "operationType" : "UPSERT_PREPPED", + "writeStats" : [ { + "fileId" : "files-0000", + "path" : "files/.files-0000_00000000000000.log.2_0-83-93", + "prevCommit" : "00000000000000", + "numWrites" : 4, + "numDeletes" : 0, + "numUpdateWrites" : 4, + "numInserts" : 0, + "totalWriteBytes" : 11180, + "totalWriteErrors" : 0, + "tempPath" : null, + "partitionPath" : "files", + "totalLogRecords" : 0, + "totalLogFilesCompacted" : 0, + "totalLogSizeCompacted" : 0, + "totalUpdatedRecordsCompacted" : 0, + "totalLogBlocks" : 0, + "totalCorruptLogBlock" : 0, + "totalRollbackBlocks" : 0, + "fileSizeInBytes" : 11180, + "minEventTime" : null, + "maxEventTime" : null, + "logVersion" : 2, + "logOffset" : 0, + "baseFile" : "", + "logFiles" : [ ".files-0000_00000000000000.log.1_0-52-57", ".files-0000_00000000000000.log.1_0-0-0", ".files-0000_00000000000000.log.2_0-83-93" ], + "recordsStats" : { + "val" : null, + "present" : false + }, + "columnStats" : { + "val" : null, + "present" : false + } + } ], + "totalRecordsDeleted" : 0, + "totalLogRecordsCompacted" : 0, + "totalLogFilesCompacted" : 0, + "totalCompactedRecordsUpdated" : 0, + "totalLogFilesSize" : 0, + "totalScanTime" : 0, + "totalCreateTime" : 0, + "totalUpsertTime" : 39, + "minAndMaxEventTime" : { + "Optional.empty" : { + "val" : null, + "present" : false + } + }, + "writePartitionPaths" : [ "files" ], + "fileIdAndRelativePaths" : { + "files-0000" : "files/.files-0000_00000000000000.log.2_0-83-93" + } +} \ No newline at end of file diff --git a/tests/integration/test_storage_hudi/test_table/.hoodie/metadata/.hoodie/20220830083647456.deltacommit.inflight b/tests/integration/test_storage_hudi/test_table/.hoodie/metadata/.hoodie/20220830083647456.deltacommit.inflight new file mode 100644 index 00000000000..ea1b6a10c13 --- /dev/null +++ b/tests/integration/test_storage_hudi/test_table/.hoodie/metadata/.hoodie/20220830083647456.deltacommit.inflight @@ -0,0 +1,116 @@ +{ + "partitionToWriteStats" : { + "files" : [ { + "fileId" : "", + "path" : null, + "prevCommit" : "null", + "numWrites" : 0, + "numDeletes" : 0, + "numUpdateWrites" : 0, + "numInserts" : 0, + "totalWriteBytes" : 0, + "totalWriteErrors" : 0, + "tempPath" : null, + "partitionPath" : null, + "totalLogRecords" : 0, + "totalLogFilesCompacted" : 0, + "totalLogSizeCompacted" : 0, + "totalUpdatedRecordsCompacted" : 0, + "totalLogBlocks" : 0, + "totalCorruptLogBlock" : 0, + "totalRollbackBlocks" : 0, + "fileSizeInBytes" : 0, + "minEventTime" : null, + "maxEventTime" : null + }, { + "fileId" : "files-0000", + "path" : null, + "prevCommit" : "00000000000000", + "numWrites" : 0, + "numDeletes" : 0, + "numUpdateWrites" : 4, + "numInserts" : 0, + "totalWriteBytes" : 0, + "totalWriteErrors" : 0, + "tempPath" : null, + "partitionPath" : null, + "totalLogRecords" : 0, + "totalLogFilesCompacted" : 0, + "totalLogSizeCompacted" : 0, + "totalUpdatedRecordsCompacted" : 0, + "totalLogBlocks" : 0, + "totalCorruptLogBlock" : 0, + "totalRollbackBlocks" : 0, + "fileSizeInBytes" : 0, + "minEventTime" : null, + "maxEventTime" : null + } ] + }, + "compacted" : false, + "extraMetadata" : { }, + "operationType" : "UPSERT_PREPPED", + "writeStats" : [ { + "fileId" : "", + "path" : null, + "prevCommit" : "null", + "numWrites" : 0, + "numDeletes" : 0, + "numUpdateWrites" : 0, + "numInserts" : 0, + "totalWriteBytes" : 0, + "totalWriteErrors" : 0, + "tempPath" : null, + "partitionPath" : null, + "totalLogRecords" : 0, + "totalLogFilesCompacted" : 0, + "totalLogSizeCompacted" : 0, + "totalUpdatedRecordsCompacted" : 0, + "totalLogBlocks" : 0, + "totalCorruptLogBlock" : 0, + "totalRollbackBlocks" : 0, + "fileSizeInBytes" : 0, + "minEventTime" : null, + "maxEventTime" : null + }, { + "fileId" : "files-0000", + "path" : null, + "prevCommit" : "00000000000000", + "numWrites" : 0, + "numDeletes" : 0, + "numUpdateWrites" : 4, + "numInserts" : 0, + "totalWriteBytes" : 0, + "totalWriteErrors" : 0, + "tempPath" : null, + "partitionPath" : null, + "totalLogRecords" : 0, + "totalLogFilesCompacted" : 0, + "totalLogSizeCompacted" : 0, + "totalUpdatedRecordsCompacted" : 0, + "totalLogBlocks" : 0, + "totalCorruptLogBlock" : 0, + "totalRollbackBlocks" : 0, + "fileSizeInBytes" : 0, + "minEventTime" : null, + "maxEventTime" : null + } ], + "totalRecordsDeleted" : 0, + "totalLogRecordsCompacted" : 0, + "totalLogFilesCompacted" : 0, + "totalCompactedRecordsUpdated" : 0, + "totalLogFilesSize" : 0, + "totalScanTime" : 0, + "totalCreateTime" : 0, + "totalUpsertTime" : 0, + "minAndMaxEventTime" : { + "Optional.empty" : { + "val" : null, + "present" : false + } + }, + "writePartitionPaths" : [ "files" ], + "fileIdAndRelativePaths" : { + "" : null, + "files-0000" : null + } +} \ No newline at end of file diff --git a/tests/integration/test_storage_hudi/test_table/.hoodie/metadata/.hoodie/20220830083647456.deltacommit.requested b/tests/integration/test_storage_hudi/test_table/.hoodie/metadata/.hoodie/20220830083647456.deltacommit.requested new file mode 100644 index 00000000000..e69de29bb2d diff --git a/tests/integration/test_storage_hudi/test_table/.hoodie/metadata/.hoodie/hoodie.properties b/tests/integration/test_storage_hudi/test_table/.hoodie/metadata/.hoodie/hoodie.properties new file mode 100644 index 00000000000..845df718f6d --- /dev/null +++ b/tests/integration/test_storage_hudi/test_table/.hoodie/metadata/.hoodie/hoodie.properties @@ -0,0 +1,14 @@ +#Properties saved on 2022-08-30T08:36:47.657528Z +#Tue Aug 30 08:36:47 UTC 2022 +hoodie.compaction.payload.class=org.apache.hudi.metadata.HoodieMetadataPayload +hoodie.table.type=MERGE_ON_READ +hoodie.archivelog.folder=archived +hoodie.timeline.layout.version=1 +hoodie.table.checksum=1983687495 +hoodie.datasource.write.drop.partition.columns=false +hoodie.table.recordkey.fields=key +hoodie.table.name=test_table_metadata +hoodie.populate.meta.fields=false +hoodie.table.keygenerator.class=org.apache.hudi.metadata.HoodieTableMetadataKeyGenerator +hoodie.table.base.file.format=HFILE +hoodie.table.version=5 diff --git a/tests/integration/test_storage_hudi/test_table/.hoodie/metadata/files/..files-0000_00000000000000.log.1_0-0-0.crc b/tests/integration/test_storage_hudi/test_table/.hoodie/metadata/files/..files-0000_00000000000000.log.1_0-0-0.crc new file mode 100644 index 0000000000000000000000000000000000000000..e016a7f52627a5b0d3862ad6a1f662fc3ad8d94a GIT binary patch literal 12 TcmYc;N@ieSU}9ifcZC}O5mf@t literal 0 HcmV?d00001 diff --git a/tests/integration/test_storage_hudi/test_table/.hoodie/metadata/files/..files-0000_00000000000000.log.1_0-52-57.crc b/tests/integration/test_storage_hudi/test_table/.hoodie/metadata/files/..files-0000_00000000000000.log.1_0-52-57.crc new file mode 100644 index 0000000000000000000000000000000000000000..c1136be0c0e257f2aa3aeed6a5c32dd7e097379c GIT binary patch literal 96 zcmYc;N@ieSU}De?RQxLt_>_I4%7)XcTl}8oT=)}k%-!ySA(_>TZG7lbpTeoRdX$3CKW0JTK069!9Qvd(} literal 0 HcmV?d00001 diff --git a/tests/integration/test_storage_hudi/test_table/.hoodie/metadata/files/..files-0000_00000000000000.log.2_0-83-93.crc b/tests/integration/test_storage_hudi/test_table/.hoodie/metadata/files/..files-0000_00000000000000.log.2_0-83-93.crc new file mode 100644 index 0000000000000000000000000000000000000000..0aaf9e50256c454e3ecb14ec9a5c5fae645fa37d GIT binary patch literal 96 zcmYc;N@ieSU}ES={`d8LX<`T0sRhSg8n$*$xud%G6-VxaBWxdUt-gH5P1~*4QmFm& gwYUbcDec;eKAvHn%Cvog%j`|7N-J<-`{&K~0Q~|n`~Uy| literal 0 HcmV?d00001 diff --git a/tests/integration/test_storage_hudi/test_table/.hoodie/metadata/files/..hoodie_partition_metadata.crc b/tests/integration/test_storage_hudi/test_table/.hoodie/metadata/files/..hoodie_partition_metadata.crc new file mode 100644 index 0000000000000000000000000000000000000000..1b5364eed343b390fb97133b3c42ae8d9e1c6af5 GIT binary patch literal 12 TcmYc;N@ieSU}9K*=PV-t6l(*N literal 0 HcmV?d00001 diff --git a/tests/integration/test_storage_hudi/test_table/.hoodie/metadata/files/.files-0000_00000000000000.log.1_0-0-0 b/tests/integration/test_storage_hudi/test_table/.hoodie/metadata/files/.files-0000_00000000000000.log.1_0-0-0 new file mode 100644 index 0000000000000000000000000000000000000000..9bf687c1a4b5fe36c79ae9759b132a53717475c4 GIT binary patch literal 124 zcmY%P2zBvPW`KY^AjOD;L85#HC;%h`1QtNd1jGi64ADOMMd^Bp1&PTSsd^cuDVci7 j`MJ6Id3w3|DXBSnE~z=GC8PcPz0)n2wJ|VGo!@#RMsVc`%MB zneYAtA;HizP6+rcN1fy<3Fd^vkc5+xke~`+MnZ_ekj0SN zE<_b)BmLVY$r9i<1DTXQ}6zuVKq85Tk&SDUW38fOFMKlJ0@ichf zhH;gGS%;!Y441(X+7+3lQ7NKQ(69$HB#GCTJqlfe6EDP|dXJ*jX#5K?7mbiKMq>mn zibiPp3o$9h6sU;x?0-X`{swiz(Kvtyv+iGo#2`+ZpFH3YwR4j6oWROW3j)S)nsFGb z6(JBJL`Ye6C^fp%$OqOlz-)zzr)X5El~6B+bl8+gG`JaqC{!p=mhR{NweAZR5a3cq z8of11VF_I#gcSsko)&)yxTVFX&BSA@C+}8o_W1!oWUACBldaLZS#24e7*1K?p8k7ozmf zC3D&-l-38&R}h2}utKBcz?ApTt)3~}#(ltw>J3LR3o#$TN?_Aq3knJh>+OYZBGS2a zFiO-qiv^-xiZKU?LEzHgH4|PUW${(_#`u zfIys8(ZB^jD0-4-c~Lt+2L_Icr-7)Sxt}NuN62`H837?AiNxz08-x_Qk%Qtium#Q# zCUMpX5t1qp84T2@6P#8TkN}sUam*>u_a)FXK|+W^0Ws>*Xik#{R21+vt;qp>MQ(3G zp;F=@G(uC_m2^aCGl9-K{VPz7Nmb?d+dav-y?7$=!Ae{bVW@dhGR;Vk|`XSzDC{jlL#(DoghBw zFeDIYN!a3#tqIu*5Td}d1SP_QVnI2M;GiVOV^9)=d!{7Z`Yu#gm=45pp!DlIz`Ry4#?qU^*dtt1C zTIla_%DU2Qk0Grd`z#hN<_9cw1m72d*W6Cc1)fvB#*7J{y3v$*2bVZ9 zG6TG>O2=+U52~s9$l0IgSjDTVspW1-m*sbkG2)GmEG;vJjb1MLcHCSr!7*!}y})Nh za8Qtt`8prGxIOI)_3bCWXS`rUPDe-C-n|_uWw~2xw$}W0@_I+y2ah_(mJZ{jq4hE@ zF!Cc6R;XupBKS#rMQU-+>#{psdy{j)E8fai5U@nB%ohMMzQy5K`Sbu}GNNl@`HD9Q z+j(OxE|}#kSyEnM2}O1C-e*&hf6VGEx&@7Yz`v}WC^h^)?s2ksZ#Z%uCzox0Op9(uVL7qxkO zDYEf8SikDtf^5&);&Y;tc=6>s-#G>ccNG6mTd z@Q6tt>|L^+oJ8Cu&EJu`$CYdn7kZr&A1zwjcmm#Bmwv%)f^EA)ds^YejJ(X4Npf=C zNsFMb8dHB6_37px5-X*N>$$f_G}0#~_*nv=guqWxM%+;(>3$$r(P?nAKF&U#R|`_D03avoH#PrchiRW4nehPWlQ+3 zCwBWcjGj2&?o8X2#$UqnzRAB(1PrrZ#7U+u}nYp>ai~WA@UT?wT;on`pC^CynKG!ZqB#m_{f7xGqE$Nhsc~iW(qF_O?)5D-rtnGJF2!|^vg$f+>zz&BEu?N zuBJG+q`aK!V3yS~qugY+gKOmJSFWEHIQY!nG^1MSbRu_8tWPQ4YPo&2S;7vp_1vm_ z?vLSl$AyT|5zokorUf6rX}xh@-b$~~_4fHz=6{}ZT+hb}UE9OXjtLYUT9xNHbHD!- zx5+7W4uL;rJkF`F9PXC7rE=VHRJ6^pSeE{q;=8sA=jQ9yk6YUwtbR-NbT22aKxz3| zx#fgpmnw-Oy=X_Br`c`C$L9P|rVWyWVb!boS4A^O`%lLe@>(V9#cTM7hkt2*XPXex5XC^6AjmBSMV?=m06!x+$sn-w2K z*m8ShVi1Nk6ynF@D$s4knU&(~HDmn#^7Fch-|aMU_`!9Xj}?d0d^;ph7|cX|*e<}d z)o5(wL@so3=?tUU9CxGFjQlxoa8}Ok`G*olfWH#htmvfvJIbG_9G5Ql2Dug?5pXM# z`J2md85l4yU|_(&fPn!60|o{R3>X+NFkoQ7z<_}P0|Wmb41i~%U5`aMj`{xqt$WqU literal 0 HcmV?d00001 diff --git a/tests/integration/test_storage_hudi/test_table/.hoodie/metadata/files/.files-0000_00000000000000.log.2_0-83-93 b/tests/integration/test_storage_hudi/test_table/.hoodie/metadata/files/.files-0000_00000000000000.log.2_0-83-93 new file mode 100644 index 0000000000000000000000000000000000000000..13a41e55cb2603f59eaf78103707eed6f56d0e61 GIT binary patch literal 11180 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 literal 0 HcmV?d00001 diff --git a/tests/integration/test_storage_hudi/test_table/.hoodie/metadata/files/.hoodie_partition_metadata b/tests/integration/test_storage_hudi/test_table/.hoodie/metadata/files/.hoodie_partition_metadata new file mode 100644 index 00000000000..abeb826f8a4 --- /dev/null +++ b/tests/integration/test_storage_hudi/test_table/.hoodie/metadata/files/.hoodie_partition_metadata @@ -0,0 +1,4 @@ +#partition metadata +#Tue Aug 30 08:36:48 UTC 2022 +commitTime=00000000000000 +partitionDepth=1 diff --git a/tests/integration/test_storage_hudi/test_table/americas/brazil/sao_paulo/..hoodie_partition_metadata.crc b/tests/integration/test_storage_hudi/test_table/americas/brazil/sao_paulo/..hoodie_partition_metadata.crc new file mode 100644 index 0000000000000000000000000000000000000000..901b972aff0c2c1daa875aaf7ffe23d1fd77e34f GIT binary patch literal 12 TcmYc;N@ieSU}9)DlYRvN5nclQ literal 0 HcmV?d00001 diff --git a/tests/integration/test_storage_hudi/test_table/americas/brazil/sao_paulo/.8a9a08bb-8cbc-4ec9-a2d4-8a6cdcaebbad-0_0-73-83_20220830083647456.parquet.crc b/tests/integration/test_storage_hudi/test_table/americas/brazil/sao_paulo/.8a9a08bb-8cbc-4ec9-a2d4-8a6cdcaebbad-0_0-73-83_20220830083647456.parquet.crc new file mode 100644 index 0000000000000000000000000000000000000000..eaff8b6a2b1a4363260f136c3a81f30a515c23f3 GIT binary patch literal 3432 zcmYc;N@ieSU}Et4_Q^jnZDTXn?nK7z2iUh?SX!{-Ka;_`y8G6=OaC^Clrt}?{;_xW zu>-E_;srQLpE);}OiL4z|3WPstIk$HtXbZZ=dCc4RZPdGen;n#L~P<9v8=N;X}?x< zUcx1>)jzQpmmEIv-`7$O(Kt_amnw^9iwYLc`+G)$Cj-a~oH@!qj@PI#|Kj#L~OZruZYP zy*7(~Gctd$us5V$)xhVLMQ5Zxqstq59}7}JM}hgW%T97H#3;ivl?=wfkB?#bw4==|f=+9Bv- z`1q50EUsEKyA|P6Lkqd9aZ@Eh=Jqb{EJrp!*z3w;WIi(cp&|cUWIj4O_0~L1bTJU$ zIqdK@WbyXk+7-xrWOnP-JQVjJ%h7}F@X>*v9`=x8N2-WQ_o}l;ucPa+TBg1ST@0Cj zLg_>rh`-O>b|r}J2s59KEKe+(Gw&(EIFYv~b%vRD_tf4uFt*L2V0joD8y!3Cq8~PK zLSl7l-Sg1Z+_;~gg)Ro-i#}*I!zPYPOvfgl3zr;7+*_goXFCp?zHP2cvAPSJT4b@n zs85B+d~ECnGuEXYj@!X)E^P9+#Fl-&1J3`r)!o7IB1u!&Pk gtRcgX?Dn7bagPbO+z}tYD-K<*&gZnGueNqE0Lh$Gn*aa+ literal 0 HcmV?d00001 diff --git a/tests/integration/test_storage_hudi/test_table/americas/brazil/sao_paulo/.hoodie_partition_metadata b/tests/integration/test_storage_hudi/test_table/americas/brazil/sao_paulo/.hoodie_partition_metadata new file mode 100644 index 00000000000..a8c28b02044 --- /dev/null +++ b/tests/integration/test_storage_hudi/test_table/americas/brazil/sao_paulo/.hoodie_partition_metadata @@ -0,0 +1,4 @@ +#partition metadata +#Tue Aug 30 08:36:50 UTC 2022 +commitTime=20220830083647456 +partitionDepth=3 diff --git a/tests/integration/test_storage_hudi/test_table/americas/brazil/sao_paulo/8a9a08bb-8cbc-4ec9-a2d4-8a6cdcaebbad-0_0-73-83_20220830083647456.parquet b/tests/integration/test_storage_hudi/test_table/americas/brazil/sao_paulo/8a9a08bb-8cbc-4ec9-a2d4-8a6cdcaebbad-0_0-73-83_20220830083647456.parquet new file mode 100644 index 0000000000000000000000000000000000000000..d929431ccd391e2d88189be66c42046c70a86252 GIT binary patch literal 437831 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 literal 0 HcmV?d00001 diff --git a/tests/integration/test_storage_hudi/test_table/americas/united_states/san_francisco/.hoodie_partition_metadata b/tests/integration/test_storage_hudi/test_table/americas/united_states/san_francisco/.hoodie_partition_metadata new file mode 100644 index 00000000000..a8c28b02044 --- /dev/null +++ b/tests/integration/test_storage_hudi/test_table/americas/united_states/san_francisco/.hoodie_partition_metadata @@ -0,0 +1,4 @@ +#partition metadata +#Tue Aug 30 08:36:50 UTC 2022 +commitTime=20220830083647456 +partitionDepth=3 diff --git a/tests/integration/test_storage_hudi/test_table/americas/united_states/san_francisco/34b1b177-f0af-467b-9214-473ead268e55-0_1-73-84_20220830083647456.parquet b/tests/integration/test_storage_hudi/test_table/americas/united_states/san_francisco/34b1b177-f0af-467b-9214-473ead268e55-0_1-73-84_20220830083647456.parquet new file mode 100644 index 0000000000000000000000000000000000000000..053841a0b91b8966c1f374a245825dc5ac0de548 GIT binary patch literal 438186 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 F{vYhxb9w*( literal 0 HcmV?d00001 diff --git a/tests/integration/test_storage_hudi/test_table/asia/india/chennai/..hoodie_partition_metadata.crc b/tests/integration/test_storage_hudi/test_table/asia/india/chennai/..hoodie_partition_metadata.crc new file mode 100644 index 0000000000000000000000000000000000000000..901b972aff0c2c1daa875aaf7ffe23d1fd77e34f GIT binary patch literal 12 TcmYc;N@ieSU}9)DlYRvN5nclQ literal 0 HcmV?d00001 diff --git a/tests/integration/test_storage_hudi/test_table/asia/india/chennai/.92aa634e-d83f-4057-a385-ea3b22e5d6e1-0_2-73-85_20220830083647456.parquet.crc b/tests/integration/test_storage_hudi/test_table/asia/india/chennai/.92aa634e-d83f-4057-a385-ea3b22e5d6e1-0_2-73-85_20220830083647456.parquet.crc new file mode 100644 index 0000000000000000000000000000000000000000..9fae4a530439b64300fa078deaf19eda992646cc GIT binary patch literal 3428 zcmYc;N@ieSU}9LgovlRl$*&o!w7J$i+&5ESa<^O7g0GXplb&tertr)`@_*KkCYS!s zSp_9GrruK^z`?M=oej-IZNbM&{?$(3GN^?ooM@W50 zN|X#fHG3AANrU9gr}Lb{rw&=}yO~EOG9MdTP<+czY~u71o4t5qBDL*$VO%drZ8J$V zZ`zv^;5^-XD1e7ly)gBfQ!8y@Y(lhQO|2?SjlY1mGmMRmUc8AR9Gf`0nA`W2OVGtY z{FJ3riPDcvFSW&_EK2SYGE@rj|2x;g5>)IAQ5V}Y?@baJ(l0gR1I-$~D9L*^61 z{=O%omKb%|)Nov|slp~steB~Wv?D&XKhAtOj!%9t$vx>ln}^LksYhh|&xoZ~Tk|S39;q5B@qb@UJVd_ITCTGFeLzTABT;@Pe vx2w(Ar)TD${v2lK^_B8BVQg%)Ps42ciE@c=u*reM=EWYrk1Y3UV;L&|A4@|J literal 0 HcmV?d00001 diff --git a/tests/integration/test_storage_hudi/test_table/asia/india/chennai/.hoodie_partition_metadata b/tests/integration/test_storage_hudi/test_table/asia/india/chennai/.hoodie_partition_metadata new file mode 100644 index 00000000000..a8c28b02044 --- /dev/null +++ b/tests/integration/test_storage_hudi/test_table/asia/india/chennai/.hoodie_partition_metadata @@ -0,0 +1,4 @@ +#partition metadata +#Tue Aug 30 08:36:50 UTC 2022 +commitTime=20220830083647456 +partitionDepth=3 diff --git a/tests/integration/test_storage_hudi/test_table/asia/india/chennai/92aa634e-d83f-4057-a385-ea3b22e5d6e1-0_2-73-85_20220830083647456.parquet b/tests/integration/test_storage_hudi/test_table/asia/india/chennai/92aa634e-d83f-4057-a385-ea3b22e5d6e1-0_2-73-85_20220830083647456.parquet new file mode 100644 index 0000000000000000000000000000000000000000..c1ba250ed09695e1f95880fc1bf3457620be17b2 GIT binary patch literal 437623 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 GtN$O{ABc|t literal 0 HcmV?d00001 From 2abe284859263d7285903196884bcf1de6debcfd Mon Sep 17 00:00:00 2001 From: Daniil Rubin Date: Tue, 30 Aug 2022 17:38:02 +0000 Subject: [PATCH 007/266] Fix engine bug --- src/Storages/StorageHudi.cpp | 11 ++++++----- src/Storages/StorageHudi.h | 1 + 2 files changed, 7 insertions(+), 5 deletions(-) diff --git a/src/Storages/StorageHudi.cpp b/src/Storages/StorageHudi.cpp index efba2d3f85f..b4eba258d0e 100644 --- a/src/Storages/StorageHudi.cpp +++ b/src/Storages/StorageHudi.cpp @@ -33,6 +33,7 @@ StorageHudi::StorageHudi( : IStorage(table_id_) , base_configuration({uri_, access_key_, secret_access_key_, {}, {}, {}}) , log(&Poco::Logger::get("StorageHudi (" + table_id_.table_name + ")")) + , table_path(uri_.key) { StorageInMemoryMetadata storage_metadata; updateS3Configuration(context_, base_configuration); @@ -42,7 +43,7 @@ StorageHudi::StorageHudi( auto new_uri = base_configuration.uri.uri.toString() + generateQueryFromKeys(std::move(keys)); LOG_DEBUG(log, "New uri: {}", new_uri); - + LOG_DEBUG(log, "Table path: {}", table_path); auto s3_uri = S3::URI(Poco::URI(new_uri)); if (columns_.empty()) @@ -143,9 +144,9 @@ std::vector StorageHudi::getKeysFromS3() bool is_finished{false}; const auto bucket{base_configuration.uri.bucket}; - const std::string key = ""; request.SetBucket(bucket); + request.SetPrefix(table_path); while (!is_finished) { @@ -155,16 +156,16 @@ std::vector StorageHudi::getKeysFromS3() ErrorCodes::S3_ERROR, "Could not list objects in bucket {} with key {}, S3 exception: {}, message: {}", quoteString(bucket), - quoteString(key), + quoteString(table_path), backQuote(outcome.GetError().GetExceptionName()), quoteString(outcome.GetError().GetMessage())); const auto & result_batch = outcome.GetResult().GetContents(); for (const auto & obj : result_batch) { - const auto & filename = obj.GetKey(); + const auto & filename = obj.GetKey().substr(table_path.size()); // object name without tablepath prefix keys.push_back(filename); - //LOG_DEBUG(log, "Found file: {}", filename); + LOG_DEBUG(log, "Found file: {}", filename); } request.SetContinuationToken(outcome.GetResult().GetNextContinuationToken()); diff --git a/src/Storages/StorageHudi.h b/src/Storages/StorageHudi.h index dd5cc18495e..47dff1c2a7b 100644 --- a/src/Storages/StorageHudi.h +++ b/src/Storages/StorageHudi.h @@ -53,6 +53,7 @@ private: StorageS3::S3Configuration base_configuration; std::shared_ptr s3engine; Poco::Logger * log; + String table_path; }; } From 6b95faf2b3a9bdae644156dcd0764030a9bb570a Mon Sep 17 00:00:00 2001 From: Daniil Rubin Date: Tue, 30 Aug 2022 17:38:57 +0000 Subject: [PATCH 008/266] Add createquery test --- .../test_storage_hudi/configs/conf.xml | 11 +++++++++++ tests/integration/test_storage_hudi/test.py | 16 ++++++++++------ 2 files changed, 21 insertions(+), 6 deletions(-) create mode 100644 tests/integration/test_storage_hudi/configs/conf.xml diff --git a/tests/integration/test_storage_hudi/configs/conf.xml b/tests/integration/test_storage_hudi/configs/conf.xml new file mode 100644 index 00000000000..e3e8627d95e --- /dev/null +++ b/tests/integration/test_storage_hudi/configs/conf.xml @@ -0,0 +1,11 @@ + + + + + http://nginx:80/test_{_partition_id} + PUT + TSV + column1 UInt32, column2 UInt32, column3 UInt32 + + + diff --git a/tests/integration/test_storage_hudi/test.py b/tests/integration/test_storage_hudi/test.py index 716ec15524e..b2f6048f408 100644 --- a/tests/integration/test_storage_hudi/test.py +++ b/tests/integration/test_storage_hudi/test.py @@ -1,5 +1,6 @@ import logging import os +import json import helpers.client import pytest @@ -50,17 +51,19 @@ def prepare_s3_bucket(started_cluster): def upload_test_table(started_cluster): bucket = started_cluster.minio_bucket - for address, dirs, files in os.walk(f"{SCRIPT_DIR}/test_table"): + for address, dirs, files in os.walk(SCRIPT_DIR + "/test_table"): + address_without_prefix = address[len(SCRIPT_DIR):] + for name in files: - started_cluster.minio_client.fput_object(bucket, os.path.join(SCRIPT_DIR, address, name), os.path.join(address, name)) + started_cluster.minio_client.fput_object(bucket, os.path.join(address_without_prefix, name), os.path.join(address, name)) for obj in list( - minio.list_objects( + started_cluster.minio_client.list_objects( bucket, recursive=True, ) ): - logging.info(obj.name) + logging.info(obj.object_name) @pytest.fixture(scope="module") def started_cluster(): @@ -68,6 +71,7 @@ def started_cluster(): cluster = ClickHouseCluster(__file__) cluster.add_instance( "dummy", + main_configs=["configs/conf.xml"], with_minio=True ) @@ -99,9 +103,9 @@ def test_create_query(started_cluster): instance = started_cluster.instances["dummy"] bucket = started_cluster.minio_bucket - create_query = f"""CREATE TABLE hudi ENGINE=Hudi('http://{started_cluster.minio_ip}:{started_cluster.minio_port}/{bucket}/hudi', 'minio', 'minio123')""" + create_query = f"""CREATE TABLE hudi ENGINE=Hudi('http://{started_cluster.minio_ip}:{started_cluster.minio_port}/{bucket}/test_table/', 'minio', 'minio123')""" run_query(instance, create_query) def test_select_query(): - pass \ No newline at end of file + pass From 7d8cc20c4600fd5fd3aab76ca84584ed65c07896 Mon Sep 17 00:00:00 2001 From: Daniil Rubin Date: Wed, 31 Aug 2022 09:26:53 +0000 Subject: [PATCH 009/266] Add select test --- .../test_storage_hudi/configs/conf.xml | 11 ----- tests/integration/test_storage_hudi/test.py | 41 +++++++++++++------ 2 files changed, 28 insertions(+), 24 deletions(-) delete mode 100644 tests/integration/test_storage_hudi/configs/conf.xml diff --git a/tests/integration/test_storage_hudi/configs/conf.xml b/tests/integration/test_storage_hudi/configs/conf.xml deleted file mode 100644 index e3e8627d95e..00000000000 --- a/tests/integration/test_storage_hudi/configs/conf.xml +++ /dev/null @@ -1,11 +0,0 @@ - - - - - http://nginx:80/test_{_partition_id} - PUT - TSV - column1 UInt32, column2 UInt32, column3 UInt32 - - - diff --git a/tests/integration/test_storage_hudi/test.py b/tests/integration/test_storage_hudi/test.py index b2f6048f408..e2d97f6489f 100644 --- a/tests/integration/test_storage_hudi/test.py +++ b/tests/integration/test_storage_hudi/test.py @@ -5,6 +5,7 @@ import json import helpers.client import pytest from helpers.cluster import ClickHouseCluster +from helpers.test_tools import TSV SCRIPT_DIR = os.path.dirname(os.path.realpath(__file__)) @@ -57,21 +58,12 @@ def upload_test_table(started_cluster): for name in files: started_cluster.minio_client.fput_object(bucket, os.path.join(address_without_prefix, name), os.path.join(address, name)) - for obj in list( - started_cluster.minio_client.list_objects( - bucket, - recursive=True, - ) - ): - logging.info(obj.object_name) - @pytest.fixture(scope="module") def started_cluster(): try: cluster = ClickHouseCluster(__file__) cluster.add_instance( - "dummy", - main_configs=["configs/conf.xml"], + "main_server", with_minio=True ) @@ -100,12 +92,35 @@ def run_query(instance, query, stdin=None, settings=None): def test_create_query(started_cluster): - instance = started_cluster.instances["dummy"] + instance = started_cluster.instances["main_server"] bucket = started_cluster.minio_bucket create_query = f"""CREATE TABLE hudi ENGINE=Hudi('http://{started_cluster.minio_ip}:{started_cluster.minio_port}/{bucket}/test_table/', 'minio', 'minio123')""" run_query(instance, create_query) -def test_select_query(): - pass +def test_select_query(started_cluster): + instance = started_cluster.instances["main_server"] + bucket = started_cluster.minio_bucket + columns = ['_hoodie_commit_time', '_hoodie_commit_seqno', '_hoodie_record_key', + '_hoodie_partition_path', '_hoodie_file_name', 'begin_lat', + 'begin_lon', 'driver', 'end_lat', 'end_lon', + 'fare', 'partitionpath', 'rider', 'ts', 'uuid'] + + # create query in case table doesn't exist + create_query = f"""CREATE TABLE IF NOT EXISTS hudi ENGINE=Hudi('http://{started_cluster.minio_ip}:{started_cluster.minio_port}/{bucket}/test_table/', 'minio', 'minio123')""" + + run_query(instance, create_query) + + select_query = "SELECT {} FROM hudi FORMAT TSV" + + for column_name in columns: + result = run_query(instance, select_query.format(column_name)).splitlines() + assert(len(result) > 0) + + # test if all partition paths is presented in result + distinct_select_query = "SELECT DISTINCT partitionpath FROM hudi FORMAT TSV" + result = run_query(instance, distinct_select_query).splitlines() + expected = ['americas/brazil/sao_paulo', 'asia/india/chennai', 'americas/united_states/san_francisco'] + + assert TSV(result) == TSV(expected) \ No newline at end of file From 0e9c3f299c75c16c05cef5b65be04fa539c2b4cd Mon Sep 17 00:00:00 2001 From: Daniil Rubin Date: Wed, 31 Aug 2022 09:47:46 +0000 Subject: [PATCH 010/266] Fix bug in test --- tests/integration/test_storage_hudi/test.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/tests/integration/test_storage_hudi/test.py b/tests/integration/test_storage_hudi/test.py index e2d97f6489f..c9415e28151 100644 --- a/tests/integration/test_storage_hudi/test.py +++ b/tests/integration/test_storage_hudi/test.py @@ -119,8 +119,8 @@ def test_select_query(started_cluster): assert(len(result) > 0) # test if all partition paths is presented in result - distinct_select_query = "SELECT DISTINCT partitionpath FROM hudi FORMAT TSV" - result = run_query(instance, distinct_select_query).splitlines() - expected = ['americas/brazil/sao_paulo', 'asia/india/chennai', 'americas/united_states/san_francisco'] - + distinct_select_query = "SELECT DISTINCT partitionpath FROM hudi ORDER BY partitionpath FORMAT TSV" + result = run_query(instance, distinct_select_query) + expected = ['americas/brazil/sao_paulo', 'americas/united_states/san_francisco', 'asia/india/chennai'] + assert TSV(result) == TSV(expected) \ No newline at end of file From fc2c8f37b1efa1e4f3cecd6583758272223da28b Mon Sep 17 00:00:00 2001 From: Daniil Rubin Date: Thu, 1 Sep 2022 19:21:53 +0000 Subject: [PATCH 011/266] Add DeltaLake storage template, file downlad from s3 --- src/Storages/StorageDelta.cpp | 294 ++++++++++++++++++++++++++++++ src/Storages/StorageDelta.h | 106 +++++++++++ src/Storages/registerStorages.cpp | 2 + 3 files changed, 402 insertions(+) create mode 100644 src/Storages/StorageDelta.cpp create mode 100644 src/Storages/StorageDelta.h diff --git a/src/Storages/StorageDelta.cpp b/src/Storages/StorageDelta.cpp new file mode 100644 index 00000000000..8da5b5ce323 --- /dev/null +++ b/src/Storages/StorageDelta.cpp @@ -0,0 +1,294 @@ +#include +#include +#include + +#include +#include +#include + +#include +#include + +#include +#include +#include + +#include + +namespace DB +{ + +namespace ErrorCodes +{ + extern const int NUMBER_OF_ARGUMENTS_DOESNT_MATCH; + extern const int S3_ERROR; +} + +void DeltaLakeMetadata::add(const String & key, uint64_t timestamp) { + file_update_time[key] = timestamp; +} + +void DeltaLakeMetadata::remove(const String & key, uint64_t /*timestamp */) { + file_update_time.erase(key); +} + +std::vector DeltaLakeMetadata::ListCurrentFiles() && { + std::vector keys; + keys.reserve(file_update_time.size()); + + for (auto && [k, _] : file_update_time) { + keys.push_back(k); + } + + return keys; +} + +JsonMetadataGetter::JsonMetadataGetter(StorageS3::S3Configuration & configuration_, + const String & table_path_, + Poco::Logger * log_) : + base_configuration(configuration_) + , table_path(table_path_) + , metadata() + , log(log_) + { + Init(); + } + +void JsonMetadataGetter::Init() { + auto keys = getJsonLogFiles(); + char localbuf[100]; + + for (const String & key : keys) { + auto buf = createS3ReadBuffer(key); + + while (!buf->eof()) { + buf->read(localbuf, 100); + + LOG_DEBUG(log, "{}", String(localbuf)); + } + } + +} + +std::vector JsonMetadataGetter::getJsonLogFiles() { + std::vector keys; + + const auto & client = base_configuration.client; + + Aws::S3::Model::ListObjectsV2Request request; + Aws::S3::Model::ListObjectsV2Outcome outcome; + + bool is_finished{false}; + const auto bucket{base_configuration.uri.bucket}; + + request.SetBucket(bucket); + request.SetPrefix(table_path + "_delta_log"); + + while (!is_finished) + { + outcome = client->ListObjectsV2(request); + if (!outcome.IsSuccess()) + throw Exception( + ErrorCodes::S3_ERROR, + "Could not list objects in bucket {} with key {}, S3 exception: {}, message: {}", + quoteString(bucket), + quoteString(table_path), + backQuote(outcome.GetError().GetExceptionName()), + quoteString(outcome.GetError().GetMessage())); + + const auto & result_batch = outcome.GetResult().GetContents(); + for (const auto & obj : result_batch) + { + const auto & filename = obj.GetKey(); + + if (filename.substr(filename.size() - 5) == ".json") + keys.push_back(filename); + } + + request.SetContinuationToken(outcome.GetResult().GetNextContinuationToken()); + is_finished = !outcome.GetResult().GetIsTruncated(); + } + + return keys; +} + +std::unique_ptr JsonMetadataGetter::createS3ReadBuffer(const String & key) { + // size_t object_size = DB::S3::getObjectSize(base_configuration.client, base_configuration.uri.bucket, key, base_configuration.uri.version_id, false); + + // TBD: add parallel downloads + return std::make_unique(base_configuration.client, base_configuration.uri.bucket, key, base_configuration.uri.version_id, /* max single read retries */ 10, ReadSettings{}); +} + +StorageDelta::StorageDelta( + const S3::URI & uri_, + const String & access_key_, + const String & secret_access_key_, + const StorageID & table_id_, + ColumnsDescription /*columns_*/, + const ConstraintsDescription & /*constraints_*/, + const String & /*comment*/, + ContextPtr context_) + : IStorage(table_id_) + , base_configuration({uri_, access_key_, secret_access_key_, {}, {}, {}}) + , log(&Poco::Logger::get("StorageDeltaLake (" + table_id_.table_name + ")")) + , table_path(uri_.key) +{ + //StorageInMemoryMetadata storage_metadata; + updateS3Configuration(context_, base_configuration); + + Init(); + + // auto keys = getKeysFromS3(); + + // auto new_uri = base_configuration.uri.uri.toString() + generateQueryFromKeys(std::move(keys)); + + // LOG_DEBUG(log, "New uri: {}", new_uri); + // LOG_DEBUG(log, "Table path: {}", table_path); + // auto s3_uri = S3::URI(Poco::URI(new_uri)); + + // if (columns_.empty()) + // { + // columns_ + // = StorageS3::getTableStructureFromData(String("Parquet"), s3_uri, access_key_, secret_access_key_, "", false, {}, context_); + // storage_metadata.setColumns(columns_); + // } + // else + // storage_metadata.setColumns(columns_); + + // storage_metadata.setConstraints(constraints_); + // storage_metadata.setComment(comment); + // setInMemoryMetadata(storage_metadata); + + // s3engine = std::make_shared( + // s3_uri, + // access_key_, + // secret_access_key_, + // table_id_, + // String("Parquet"), // format name + // base_configuration.rw_settings, + // columns_, + // constraints_, + // comment, + // context_, + // std::nullopt); +} + +void StorageDelta::Init() { + JsonMetadataGetter getter{base_configuration, table_path, log}; +} + +Pipe StorageDelta::read( + const Names & column_names, + const StorageSnapshotPtr & storage_snapshot, + SelectQueryInfo & query_info, + ContextPtr context, + QueryProcessingStage::Enum processed_stage, + size_t max_block_size, + unsigned num_streams) +{ + updateS3Configuration(context, base_configuration); + + return s3engine->read(column_names, storage_snapshot, query_info, context, processed_stage, max_block_size, num_streams); +} + +void StorageDelta::updateS3Configuration(ContextPtr ctx, StorageS3::S3Configuration & upd) +{ + auto settings = ctx->getStorageS3Settings().getSettings(upd.uri.uri.toString()); + + bool need_update_configuration = settings != S3Settings{}; + if (need_update_configuration) + { + if (upd.rw_settings != settings.rw_settings) + upd.rw_settings = settings.rw_settings; + } + + upd.rw_settings.updateFromSettingsIfEmpty(ctx->getSettings()); + + if (upd.client && (!upd.access_key_id.empty() || settings.auth_settings == upd.auth_settings)) + return; + + Aws::Auth::AWSCredentials credentials(upd.access_key_id, upd.secret_access_key); + HeaderCollection headers; + if (upd.access_key_id.empty()) + { + credentials = Aws::Auth::AWSCredentials(settings.auth_settings.access_key_id, settings.auth_settings.secret_access_key); + headers = settings.auth_settings.headers; + } + + S3::PocoHTTPClientConfiguration client_configuration = S3::ClientFactory::instance().createClientConfiguration( + settings.auth_settings.region, + ctx->getRemoteHostFilter(), + ctx->getGlobalContext()->getSettingsRef().s3_max_redirects, + ctx->getGlobalContext()->getSettingsRef().enable_s3_requests_logging); + + client_configuration.endpointOverride = upd.uri.endpoint; + client_configuration.maxConnections = upd.rw_settings.max_connections; + + upd.client = S3::ClientFactory::instance().create( + client_configuration, + upd.uri.is_virtual_hosted_style, + credentials.GetAWSAccessKeyId(), + credentials.GetAWSSecretKey(), + settings.auth_settings.server_side_encryption_customer_key_base64, + std::move(headers), + settings.auth_settings.use_environment_credentials.value_or(ctx->getConfigRef().getBool("s3.use_environment_credentials", false)), + settings.auth_settings.use_insecure_imds_request.value_or(ctx->getConfigRef().getBool("s3.use_insecure_imds_request", false))); + + upd.auth_settings = std::move(settings.auth_settings); +} + +String StorageDelta::generateQueryFromKeys(std::vector && keys) +{ + String new_query; + + for (auto && key : keys) + { + if (!new_query.empty()) + { + new_query += ","; + } + new_query += key; + } + new_query = "{" + new_query + "}"; + + return new_query; +} + + +void registerStorageDelta(StorageFactory & factory) +{ + factory.registerStorage( + "DeltaLake", + [](const StorageFactory::Arguments & args) + { + auto & engine_args = args.engine_args; + if (engine_args.empty()) + throw Exception(ErrorCodes::BAD_ARGUMENTS, "External data source must have arguments"); + + auto configuration = StorageS3::getConfiguration(engine_args, args.getLocalContext()); + + configuration.url = checkAndGetLiteralArgument(engine_args[0], "url"); + configuration.auth_settings.access_key_id = checkAndGetLiteralArgument(engine_args[1], "access_key_id"); + configuration.auth_settings.secret_access_key = checkAndGetLiteralArgument(engine_args[2], "secret_access_key"); + + + S3::URI s3_uri(Poco::URI(configuration.url)); + + return std::make_shared( + s3_uri, + configuration.auth_settings.access_key_id, + configuration.auth_settings.secret_access_key, + args.table_id, + args.columns, + args.constraints, + args.comment, + args.getContext()); + }, + { + .supports_settings = true, + .supports_schema_inference = true, + .source_access_type = AccessType::S3, + }); +} + +} diff --git a/src/Storages/StorageDelta.h b/src/Storages/StorageDelta.h new file mode 100644 index 00000000000..2aec4f815f3 --- /dev/null +++ b/src/Storages/StorageDelta.h @@ -0,0 +1,106 @@ +#pragma once + +#include "config_core.h" + +#include +#include + +#include +#include + +namespace Poco +{ +class Logger; +} + +namespace Aws::S3 +{ +class S3Client; +} + +namespace DB +{ + +// class to parse json deltalake metadata and find files needed for query in table +class DeltaLakeMetadata { +public: + DeltaLakeMetadata() = default; + +public: + void add(const String & filename, uint64_t timestamp); + void remove(const String & filename, uint64_t timestamp); + +public: + std::vector ListCurrentFiles() &&; + +private: + std::unordered_map file_update_time; +}; + +// class to get deltalake log json files and read json from them +class JsonMetadataGetter +{ +public: + JsonMetadataGetter(StorageS3::S3Configuration & configuration_, + const String & table_path_, + Poco::Logger * log_ + ); + +private: + void Init(); + + std::vector getJsonLogFiles(); + +private: + std::unique_ptr createS3ReadBuffer(const String & key); + +public: + std::vector getFiles() { return std::move(metadata).ListCurrentFiles(); } + +private: + StorageS3::S3Configuration base_configuration; + String table_path; + DeltaLakeMetadata metadata; + Poco::Logger * log; +}; + +class StorageDelta : public IStorage +{ +public: + StorageDelta( + const S3::URI & uri_, + const String & access_key_, + const String & secret_access_key_, + const StorageID & table_id_, + ColumnsDescription columns_, + const ConstraintsDescription & constraints_, + const String & comment, + ContextPtr context_); + + String getName() const override { return "DeltaLake"; } + + Pipe read( + const Names & column_names, + const StorageSnapshotPtr & storage_snapshot, + SelectQueryInfo & query_info, + ContextPtr context, + QueryProcessingStage::Enum processed_stage, + size_t max_block_size, + unsigned num_streams) override; + +private: + void Init(); + static void updateS3Configuration(ContextPtr, StorageS3::S3Configuration &); + + +private: + String generateQueryFromKeys(std::vector && keys); + +private: + StorageS3::S3Configuration base_configuration; + std::shared_ptr s3engine; + Poco::Logger * log; + String table_path; +}; + +} diff --git a/src/Storages/registerStorages.cpp b/src/Storages/registerStorages.cpp index b5561243e56..c0d153a5efa 100644 --- a/src/Storages/registerStorages.cpp +++ b/src/Storages/registerStorages.cpp @@ -89,6 +89,7 @@ void registerStorageSQLite(StorageFactory & factory); #endif void registerStorageHudi(StorageFactory & factory); +void registerStorageDelta(StorageFactory & factory); void registerStorages() { @@ -174,6 +175,7 @@ void registerStorages() #endif registerStorageHudi(factory); + registerStorageDelta(factory); } } From c68257d711cbcc61150adc9c4a503eea23063a0b Mon Sep 17 00:00:00 2001 From: Daniil Rubin Date: Fri, 2 Sep 2022 06:54:16 +0000 Subject: [PATCH 012/266] Implement StorageDelta --- src/Storages/StorageDelta.cpp | 137 ++++++++++++++++++++++------------ 1 file changed, 90 insertions(+), 47 deletions(-) diff --git a/src/Storages/StorageDelta.cpp b/src/Storages/StorageDelta.cpp index 8da5b5ce323..05b418e4208 100644 --- a/src/Storages/StorageDelta.cpp +++ b/src/Storages/StorageDelta.cpp @@ -56,15 +56,58 @@ JsonMetadataGetter::JsonMetadataGetter(StorageS3::S3Configuration & configuratio void JsonMetadataGetter::Init() { auto keys = getJsonLogFiles(); - char localbuf[100]; - + + // read data from every json log file for (const String & key : keys) { auto buf = createS3ReadBuffer(key); + String json_str; + size_t opening(0), closing(0); + char c; - while (!buf->eof()) { - buf->read(localbuf, 100); + while (buf->read(c)) { + // skip all space characters for JSON to parse correctly + if (isspace(c)) { + continue; + } + + json_str.push_back(c); + + if (c == '{') + opening++; + else if (c == '}') + closing++; + + if (opening == closing) { + + LOG_DEBUG(log, "JSON {}, {}", json_str, json_str.size()); + + JSON json(json_str); + + if (json.has("add")) { + auto path = json["add"]["path"].getString(); + auto timestamp = json["add"]["modificationTime"].getInt(); + + metadata.add(path, timestamp); + + LOG_DEBUG(log, "Path {}", path); + LOG_DEBUG(log, "Timestamp {}", timestamp); + } else if (json.has("remove")) { + auto path = json["remove"]["path"].getString(); + auto timestamp = json["remove"]["modificationTime"].getInt(); + + metadata.remove(path, timestamp); + + LOG_DEBUG(log, "Path {}", path); + LOG_DEBUG(log, "Timestamp {}", timestamp); + } + + // reset + opening = 0; + closing = 0; + json_str.clear(); + + } - LOG_DEBUG(log, "{}", String(localbuf)); } } @@ -124,57 +167,57 @@ StorageDelta::StorageDelta( const String & access_key_, const String & secret_access_key_, const StorageID & table_id_, - ColumnsDescription /*columns_*/, - const ConstraintsDescription & /*constraints_*/, - const String & /*comment*/, + ColumnsDescription columns_, + const ConstraintsDescription & constraints_, + const String & comment, ContextPtr context_) : IStorage(table_id_) , base_configuration({uri_, access_key_, secret_access_key_, {}, {}, {}}) , log(&Poco::Logger::get("StorageDeltaLake (" + table_id_.table_name + ")")) , table_path(uri_.key) { - //StorageInMemoryMetadata storage_metadata; + StorageInMemoryMetadata storage_metadata; updateS3Configuration(context_, base_configuration); - Init(); - - // auto keys = getKeysFromS3(); - - // auto new_uri = base_configuration.uri.uri.toString() + generateQueryFromKeys(std::move(keys)); - - // LOG_DEBUG(log, "New uri: {}", new_uri); - // LOG_DEBUG(log, "Table path: {}", table_path); - // auto s3_uri = S3::URI(Poco::URI(new_uri)); - - // if (columns_.empty()) - // { - // columns_ - // = StorageS3::getTableStructureFromData(String("Parquet"), s3_uri, access_key_, secret_access_key_, "", false, {}, context_); - // storage_metadata.setColumns(columns_); - // } - // else - // storage_metadata.setColumns(columns_); - - // storage_metadata.setConstraints(constraints_); - // storage_metadata.setComment(comment); - // setInMemoryMetadata(storage_metadata); - - // s3engine = std::make_shared( - // s3_uri, - // access_key_, - // secret_access_key_, - // table_id_, - // String("Parquet"), // format name - // base_configuration.rw_settings, - // columns_, - // constraints_, - // comment, - // context_, - // std::nullopt); -} - -void StorageDelta::Init() { JsonMetadataGetter getter{base_configuration, table_path, log}; + + auto keys = getter.getFiles(); + + for (const String & path : keys) { + LOG_DEBUG(log, "{}", path); + } + + auto new_uri = base_configuration.uri.uri.toString() + generateQueryFromKeys(std::move(keys)); + + LOG_DEBUG(log, "New uri: {}", new_uri); + LOG_DEBUG(log, "Table path: {}", table_path); + auto s3_uri = S3::URI(Poco::URI(new_uri)); + + if (columns_.empty()) + { + columns_ + = StorageS3::getTableStructureFromData(String("Parquet"), s3_uri, access_key_, secret_access_key_, "", false, {}, context_); + storage_metadata.setColumns(columns_); + } + else + storage_metadata.setColumns(columns_); + + storage_metadata.setConstraints(constraints_); + storage_metadata.setComment(comment); + setInMemoryMetadata(storage_metadata); + + s3engine = std::make_shared( + s3_uri, + access_key_, + secret_access_key_, + table_id_, + String("Parquet"), // format name + base_configuration.rw_settings, + columns_, + constraints_, + comment, + context_, + std::nullopt); } Pipe StorageDelta::read( From 4cc397da84e6bf3bd6157b7be7ce578cb02aba4c Mon Sep 17 00:00:00 2001 From: Daniil Rubin Date: Fri, 2 Sep 2022 07:06:24 +0000 Subject: [PATCH 013/266] Add tests for deltalake storage --- .../test_storage_delta/__init__.py | 0 tests/integration/test_storage_delta/test.py | 118 ++++++++++++++++++ .../_delta_log/.00000000000000000000.json.crc | Bin 0 -> 32 bytes .../_delta_log/.00000000000000000001.json.crc | Bin 0 -> 40 bytes .../_delta_log/00000000000000000000.json | 9 ++ .../_delta_log/00000000000000000001.json | 13 ++ ...-831e-2ab223e7c176.c000.snappy.parquet.crc | Bin 0 -> 32 bytes ...-b556-cd6ebe7630c9.c000.snappy.parquet.crc | Bin 0 -> 32 bytes ...-b929-a68f54aa1e6b.c000.snappy.parquet.crc | Bin 0 -> 32 bytes ...-8144-d0d9d0ff572c.c000.snappy.parquet.crc | Bin 0 -> 32 bytes ...42ca-831e-2ab223e7c176.c000.snappy.parquet | Bin 0 -> 2795 bytes ...4514-b556-cd6ebe7630c9.c000.snappy.parquet | Bin 0 -> 2795 bytes ...42de-b929-a68f54aa1e6b.c000.snappy.parquet | Bin 0 -> 2878 bytes ...493a-8144-d0d9d0ff572c.c000.snappy.parquet | Bin 0 -> 2878 bytes ...-afe4-658c02e1aeb5.c000.snappy.parquet.crc | Bin 0 -> 32 bytes ...-a37c-1539c1bb57b1.c000.snappy.parquet.crc | Bin 0 -> 32 bytes ...-b8e2-817f80097b3b.c000.snappy.parquet.crc | Bin 0 -> 32 bytes ...-8135-23184ffdc617.c000.snappy.parquet.crc | Bin 0 -> 32 bytes ...41e5-afe4-658c02e1aeb5.c000.snappy.parquet | Bin 0 -> 2966 bytes ...47f5-a37c-1539c1bb57b1.c000.snappy.parquet | Bin 0 -> 2966 bytes ...466b-b8e2-817f80097b3b.c000.snappy.parquet | Bin 0 -> 2878 bytes ...4fe8-8135-23184ffdc617.c000.snappy.parquet | Bin 0 -> 2879 bytes ...-acd2-d2bab8e66748.c000.snappy.parquet.crc | Bin 0 -> 32 bytes ...-841b-22762fcfc509.c000.snappy.parquet.crc | Bin 0 -> 32 bytes ...-9527-a3dcd269f99e.c000.snappy.parquet.crc | Bin 0 -> 32 bytes ...-8ca0-7594340b2c66.c000.snappy.parquet.crc | Bin 0 -> 32 bytes ...424f-acd2-d2bab8e66748.c000.snappy.parquet | Bin 0 -> 2795 bytes ...41e9-841b-22762fcfc509.c000.snappy.parquet | Bin 0 -> 2794 bytes ...463a-9527-a3dcd269f99e.c000.snappy.parquet | Bin 0 -> 2795 bytes ...45c7-8ca0-7594340b2c66.c000.snappy.parquet | Bin 0 -> 2795 bytes 30 files changed, 140 insertions(+) create mode 100644 tests/integration/test_storage_delta/__init__.py create mode 100644 tests/integration/test_storage_delta/test.py create mode 100644 tests/integration/test_storage_delta/test_table/_delta_log/.00000000000000000000.json.crc create mode 100644 tests/integration/test_storage_delta/test_table/_delta_log/.00000000000000000001.json.crc create mode 100644 tests/integration/test_storage_delta/test_table/_delta_log/00000000000000000000.json create mode 100644 tests/integration/test_storage_delta/test_table/_delta_log/00000000000000000001.json create mode 100644 tests/integration/test_storage_delta/test_table/partitionpath=americas%2Fbrazil%2Fsao_paulo/.part-00000-7212b9be-df70-42ca-831e-2ab223e7c176.c000.snappy.parquet.crc create mode 100644 tests/integration/test_storage_delta/test_table/partitionpath=americas%2Fbrazil%2Fsao_paulo/.part-00000-df1117a8-d568-4514-b556-cd6ebe7630c9.c000.snappy.parquet.crc create mode 100644 tests/integration/test_storage_delta/test_table/partitionpath=americas%2Fbrazil%2Fsao_paulo/.part-00001-3fd0374b-5fcf-42de-b929-a68f54aa1e6b.c000.snappy.parquet.crc create mode 100644 tests/integration/test_storage_delta/test_table/partitionpath=americas%2Fbrazil%2Fsao_paulo/.part-00001-d0760f2d-45e8-493a-8144-d0d9d0ff572c.c000.snappy.parquet.crc create mode 100644 tests/integration/test_storage_delta/test_table/partitionpath=americas%2Fbrazil%2Fsao_paulo/part-00000-7212b9be-df70-42ca-831e-2ab223e7c176.c000.snappy.parquet create mode 100644 tests/integration/test_storage_delta/test_table/partitionpath=americas%2Fbrazil%2Fsao_paulo/part-00000-df1117a8-d568-4514-b556-cd6ebe7630c9.c000.snappy.parquet create mode 100644 tests/integration/test_storage_delta/test_table/partitionpath=americas%2Fbrazil%2Fsao_paulo/part-00001-3fd0374b-5fcf-42de-b929-a68f54aa1e6b.c000.snappy.parquet create mode 100644 tests/integration/test_storage_delta/test_table/partitionpath=americas%2Fbrazil%2Fsao_paulo/part-00001-d0760f2d-45e8-493a-8144-d0d9d0ff572c.c000.snappy.parquet create mode 100644 tests/integration/test_storage_delta/test_table/partitionpath=americas%2Funited_states%2Fsan_francisco/.part-00000-8dcd9986-b57d-41e5-afe4-658c02e1aeb5.c000.snappy.parquet.crc create mode 100644 tests/integration/test_storage_delta/test_table/partitionpath=americas%2Funited_states%2Fsan_francisco/.part-00000-a8bac363-ee42-47f5-a37c-1539c1bb57b1.c000.snappy.parquet.crc create mode 100644 tests/integration/test_storage_delta/test_table/partitionpath=americas%2Funited_states%2Fsan_francisco/.part-00001-7e34b80c-8fe9-466b-b8e2-817f80097b3b.c000.snappy.parquet.crc create mode 100644 tests/integration/test_storage_delta/test_table/partitionpath=americas%2Funited_states%2Fsan_francisco/.part-00001-cebe56e9-0e6f-4fe8-8135-23184ffdc617.c000.snappy.parquet.crc create mode 100644 tests/integration/test_storage_delta/test_table/partitionpath=americas%2Funited_states%2Fsan_francisco/part-00000-8dcd9986-b57d-41e5-afe4-658c02e1aeb5.c000.snappy.parquet create mode 100644 tests/integration/test_storage_delta/test_table/partitionpath=americas%2Funited_states%2Fsan_francisco/part-00000-a8bac363-ee42-47f5-a37c-1539c1bb57b1.c000.snappy.parquet create mode 100644 tests/integration/test_storage_delta/test_table/partitionpath=americas%2Funited_states%2Fsan_francisco/part-00001-7e34b80c-8fe9-466b-b8e2-817f80097b3b.c000.snappy.parquet create mode 100644 tests/integration/test_storage_delta/test_table/partitionpath=americas%2Funited_states%2Fsan_francisco/part-00001-cebe56e9-0e6f-4fe8-8135-23184ffdc617.c000.snappy.parquet create mode 100644 tests/integration/test_storage_delta/test_table/partitionpath=asia%2Findia%2Fchennai/.part-00000-714ed689-3609-424f-acd2-d2bab8e66748.c000.snappy.parquet.crc create mode 100644 tests/integration/test_storage_delta/test_table/partitionpath=asia%2Findia%2Fchennai/.part-00000-db7e2844-bba1-41e9-841b-22762fcfc509.c000.snappy.parquet.crc create mode 100644 tests/integration/test_storage_delta/test_table/partitionpath=asia%2Findia%2Fchennai/.part-00001-a3499b25-46da-463a-9527-a3dcd269f99e.c000.snappy.parquet.crc create mode 100644 tests/integration/test_storage_delta/test_table/partitionpath=asia%2Findia%2Fchennai/.part-00001-cbd68744-0f7d-45c7-8ca0-7594340b2c66.c000.snappy.parquet.crc create mode 100644 tests/integration/test_storage_delta/test_table/partitionpath=asia%2Findia%2Fchennai/part-00000-714ed689-3609-424f-acd2-d2bab8e66748.c000.snappy.parquet create mode 100644 tests/integration/test_storage_delta/test_table/partitionpath=asia%2Findia%2Fchennai/part-00000-db7e2844-bba1-41e9-841b-22762fcfc509.c000.snappy.parquet create mode 100644 tests/integration/test_storage_delta/test_table/partitionpath=asia%2Findia%2Fchennai/part-00001-a3499b25-46da-463a-9527-a3dcd269f99e.c000.snappy.parquet create mode 100644 tests/integration/test_storage_delta/test_table/partitionpath=asia%2Findia%2Fchennai/part-00001-cbd68744-0f7d-45c7-8ca0-7594340b2c66.c000.snappy.parquet diff --git a/tests/integration/test_storage_delta/__init__.py b/tests/integration/test_storage_delta/__init__.py new file mode 100644 index 00000000000..e69de29bb2d diff --git a/tests/integration/test_storage_delta/test.py b/tests/integration/test_storage_delta/test.py new file mode 100644 index 00000000000..a1cc6345619 --- /dev/null +++ b/tests/integration/test_storage_delta/test.py @@ -0,0 +1,118 @@ +import logging +import os +import json + +import helpers.client +import pytest +from helpers.cluster import ClickHouseCluster +from helpers.test_tools import TSV + +SCRIPT_DIR = os.path.dirname(os.path.realpath(__file__)) + +def prepare_s3_bucket(started_cluster): + bucket_read_write_policy = { + "Version": "2012-10-17", + "Statement": [ + { + "Sid": "", + "Effect": "Allow", + "Principal": {"AWS": "*"}, + "Action": "s3:GetBucketLocation", + "Resource": "arn:aws:s3:::root", + }, + { + "Sid": "", + "Effect": "Allow", + "Principal": {"AWS": "*"}, + "Action": "s3:ListBucket", + "Resource": "arn:aws:s3:::root", + }, + { + "Sid": "", + "Effect": "Allow", + "Principal": {"AWS": "*"}, + "Action": "s3:GetObject", + "Resource": "arn:aws:s3:::root/*", + }, + { + "Sid": "", + "Effect": "Allow", + "Principal": {"AWS": "*"}, + "Action": "s3:PutObject", + "Resource": "arn:aws:s3:::root/*", + }, + ], + } + + minio_client = started_cluster.minio_client + minio_client.set_bucket_policy( + started_cluster.minio_bucket, json.dumps(bucket_read_write_policy) + ) + +def upload_test_table(started_cluster): + bucket = started_cluster.minio_bucket + + for address, dirs, files in os.walk(SCRIPT_DIR + "/test_table"): + address_without_prefix = address[len(SCRIPT_DIR):] + + for name in files: + started_cluster.minio_client.fput_object(bucket, os.path.join(address_without_prefix, name), os.path.join(address, name)) + +@pytest.fixture(scope="module") +def started_cluster(): + try: + cluster = ClickHouseCluster(__file__) + cluster.add_instance( + "main_server", + with_minio=True + ) + + logging.info("Starting cluster...") + cluster.start() + + prepare_s3_bucket(cluster) + logging.info("S3 bucket created") + + upload_test_table(cluster) + logging.info("Test table uploaded") + + yield cluster + + finally: + cluster.shutdown() + +def run_query(instance, query, stdin=None, settings=None): + # type: (ClickHouseInstance, str, object, dict) -> str + + logging.info("Running query '{}'...".format(query)) + result = instance.query(query, stdin=stdin, settings=settings) + logging.info("Query finished") + + return result + + +def test_create_query(started_cluster): + instance = started_cluster.instances["main_server"] + bucket = started_cluster.minio_bucket + + create_query = f"""CREATE TABLE deltalake ENGINE=DeltaLake('http://{started_cluster.minio_ip}:{started_cluster.minio_port}/{bucket}/test_table/', 'minio', 'minio123')""" + + run_query(instance, create_query) + +def test_select_query(started_cluster): + instance = started_cluster.instances["main_server"] + bucket = started_cluster.minio_bucket + columns = ['begin_lat', + 'begin_lon', 'driver', 'end_lat', 'end_lon', + 'fare', 'rider', 'ts', 'uuid'] + + # create query in case table doesn't exist + create_query = f"""CREATE TABLE IF NOT EXISTS deltalake ENGINE=DeltaLake('http://{started_cluster.minio_ip}:{started_cluster.minio_port}/{bucket}/test_table/', 'minio', 'minio123')""" + + run_query(instance, create_query) + + select_query = "SELECT {} FROM deltalake FORMAT TSV" + + for column_name in columns: + result = run_query(instance, select_query.format(column_name)).splitlines() + assert(len(result) > 0) diff --git a/tests/integration/test_storage_delta/test_table/_delta_log/.00000000000000000000.json.crc b/tests/integration/test_storage_delta/test_table/_delta_log/.00000000000000000000.json.crc new file mode 100644 index 0000000000000000000000000000000000000000..327e28983364280c30587f7c0432a548421b39d2 GIT binary patch literal 32 ocmYc;N@ieSU}6Y-KR?}UN#0|x{?hJ)>i?zFf&yZyPN~ZS0KQ@jbpQYW literal 0 HcmV?d00001 diff --git a/tests/integration/test_storage_delta/test_table/_delta_log/.00000000000000000001.json.crc b/tests/integration/test_storage_delta/test_table/_delta_log/.00000000000000000001.json.crc new file mode 100644 index 0000000000000000000000000000000000000000..fd48c97957675d0896f9a5abd1f7ab17ed1036e2 GIT binary patch literal 40 wcmYc;N@ieSU}Bibee&Zz8;eO*Q@6+W^~-ViO9mCsF7{}b3b9qWw(i1403d-6qW}N^ literal 0 HcmV?d00001 diff --git a/tests/integration/test_storage_delta/test_table/_delta_log/00000000000000000000.json b/tests/integration/test_storage_delta/test_table/_delta_log/00000000000000000000.json new file mode 100644 index 00000000000..45fd233fd48 --- /dev/null +++ b/tests/integration/test_storage_delta/test_table/_delta_log/00000000000000000000.json @@ -0,0 +1,9 @@ +{"protocol":{"minReaderVersion":1,"minWriterVersion":2}} +{"metaData":{"id":"6eae6736-e014-439d-8301-070bfa5fc358","format":{"provider":"parquet","options":{}},"schemaString":"{\"type\":\"struct\",\"fields\":[{\"name\":\"begin_lat\",\"type\":\"double\",\"nullable\":true,\"metadata\":{}},{\"name\":\"begin_lon\",\"type\":\"double\",\"nullable\":true,\"metadata\":{}},{\"name\":\"driver\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}},{\"name\":\"end_lat\",\"type\":\"double\",\"nullable\":true,\"metadata\":{}},{\"name\":\"end_lon\",\"type\":\"double\",\"nullable\":true,\"metadata\":{}},{\"name\":\"fare\",\"type\":\"double\",\"nullable\":true,\"metadata\":{}},{\"name\":\"partitionpath\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}},{\"name\":\"rider\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}},{\"name\":\"ts\",\"type\":\"long\",\"nullable\":true,\"metadata\":{}},{\"name\":\"uuid\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}}]}","partitionColumns":["partitionpath"],"configuration":{},"createdTime":1661963201495}} +{"add":{"path":"partitionpath=americas%252Fbrazil%252Fsao_paulo/part-00000-7212b9be-df70-42ca-831e-2ab223e7c176.c000.snappy.parquet","partitionValues":{"partitionpath":"americas/brazil/sao_paulo"},"size":2795,"modificationTime":1661963202988,"dataChange":true}} +{"add":{"path":"partitionpath=americas%252Funited_states%252Fsan_francisco/part-00000-8dcd9986-b57d-41e5-afe4-658c02e1aeb5.c000.snappy.parquet","partitionValues":{"partitionpath":"americas/united_states/san_francisco"},"size":2966,"modificationTime":1661963203028,"dataChange":true}} +{"add":{"path":"partitionpath=asia%252Findia%252Fchennai/part-00000-714ed689-3609-424f-acd2-d2bab8e66748.c000.snappy.parquet","partitionValues":{"partitionpath":"asia/india/chennai"},"size":2795,"modificationTime":1661963203056,"dataChange":true}} +{"add":{"path":"partitionpath=americas%252Fbrazil%252Fsao_paulo/part-00001-3fd0374b-5fcf-42de-b929-a68f54aa1e6b.c000.snappy.parquet","partitionValues":{"partitionpath":"americas/brazil/sao_paulo"},"size":2878,"modificationTime":1661963202988,"dataChange":true}} +{"add":{"path":"partitionpath=americas%252Funited_states%252Fsan_francisco/part-00001-7e34b80c-8fe9-466b-b8e2-817f80097b3b.c000.snappy.parquet","partitionValues":{"partitionpath":"americas/united_states/san_francisco"},"size":2878,"modificationTime":1661963203044,"dataChange":true}} +{"add":{"path":"partitionpath=asia%252Findia%252Fchennai/part-00001-a3499b25-46da-463a-9527-a3dcd269f99e.c000.snappy.parquet","partitionValues":{"partitionpath":"asia/india/chennai"},"size":2795,"modificationTime":1661963203072,"dataChange":true}} +{"commitInfo":{"timestamp":1661963203129,"operation":"WRITE","operationParameters":{"mode":"ErrorIfExists","partitionBy":"[\"partitionpath\"]"},"isolationLevel":"Serializable","isBlindAppend":true,"operationMetrics":{"numFiles":"6","numOutputRows":"10","numOutputBytes":"17107"},"engineInfo":"Apache-Spark/3.2.2 Delta-Lake/1.1.0"}} diff --git a/tests/integration/test_storage_delta/test_table/_delta_log/00000000000000000001.json b/tests/integration/test_storage_delta/test_table/_delta_log/00000000000000000001.json new file mode 100644 index 00000000000..408d5e1ded7 --- /dev/null +++ b/tests/integration/test_storage_delta/test_table/_delta_log/00000000000000000001.json @@ -0,0 +1,13 @@ +{"add":{"path":"partitionpath=americas%252Fbrazil%252Fsao_paulo/part-00000-df1117a8-d568-4514-b556-cd6ebe7630c9.c000.snappy.parquet","partitionValues":{"partitionpath":"americas/brazil/sao_paulo"},"size":2795,"modificationTime":1661964654518,"dataChange":true}} +{"add":{"path":"partitionpath=americas%252Funited_states%252Fsan_francisco/part-00000-a8bac363-ee42-47f5-a37c-1539c1bb57b1.c000.snappy.parquet","partitionValues":{"partitionpath":"americas/united_states/san_francisco"},"size":2966,"modificationTime":1661964654558,"dataChange":true}} +{"add":{"path":"partitionpath=asia%252Findia%252Fchennai/part-00000-db7e2844-bba1-41e9-841b-22762fcfc509.c000.snappy.parquet","partitionValues":{"partitionpath":"asia/india/chennai"},"size":2794,"modificationTime":1661964654586,"dataChange":true}} +{"add":{"path":"partitionpath=americas%252Fbrazil%252Fsao_paulo/part-00001-d0760f2d-45e8-493a-8144-d0d9d0ff572c.c000.snappy.parquet","partitionValues":{"partitionpath":"americas/brazil/sao_paulo"},"size":2878,"modificationTime":1661964654518,"dataChange":true}} +{"add":{"path":"partitionpath=americas%252Funited_states%252Fsan_francisco/part-00001-cebe56e9-0e6f-4fe8-8135-23184ffdc617.c000.snappy.parquet","partitionValues":{"partitionpath":"americas/united_states/san_francisco"},"size":2879,"modificationTime":1661964654558,"dataChange":true}} +{"add":{"path":"partitionpath=asia%252Findia%252Fchennai/part-00001-cbd68744-0f7d-45c7-8ca0-7594340b2c66.c000.snappy.parquet","partitionValues":{"partitionpath":"asia/india/chennai"},"size":2795,"modificationTime":1661964654582,"dataChange":true}} +{"remove":{"path":"partitionpath=americas%252Fbrazil%252Fsao_paulo/part-00000-7212b9be-df70-42ca-831e-2ab223e7c176.c000.snappy.parquet","deletionTimestamp":1661964655238,"dataChange":true,"extendedFileMetadata":true,"partitionValues":{"partitionpath":"americas/brazil/sao_paulo"},"size":2795}} +{"remove":{"path":"partitionpath=americas%252Funited_states%252Fsan_francisco/part-00000-8dcd9986-b57d-41e5-afe4-658c02e1aeb5.c000.snappy.parquet","deletionTimestamp":1661964655238,"dataChange":true,"extendedFileMetadata":true,"partitionValues":{"partitionpath":"americas/united_states/san_francisco"},"size":2966}} +{"remove":{"path":"partitionpath=americas%252Funited_states%252Fsan_francisco/part-00001-7e34b80c-8fe9-466b-b8e2-817f80097b3b.c000.snappy.parquet","deletionTimestamp":1661964655238,"dataChange":true,"extendedFileMetadata":true,"partitionValues":{"partitionpath":"americas/united_states/san_francisco"},"size":2878}} +{"remove":{"path":"partitionpath=asia%252Findia%252Fchennai/part-00000-714ed689-3609-424f-acd2-d2bab8e66748.c000.snappy.parquet","deletionTimestamp":1661964655238,"dataChange":true,"extendedFileMetadata":true,"partitionValues":{"partitionpath":"asia/india/chennai"},"size":2795}} +{"remove":{"path":"partitionpath=americas%252Fbrazil%252Fsao_paulo/part-00001-3fd0374b-5fcf-42de-b929-a68f54aa1e6b.c000.snappy.parquet","deletionTimestamp":1661964655238,"dataChange":true,"extendedFileMetadata":true,"partitionValues":{"partitionpath":"americas/brazil/sao_paulo"},"size":2878}} +{"remove":{"path":"partitionpath=asia%252Findia%252Fchennai/part-00001-a3499b25-46da-463a-9527-a3dcd269f99e.c000.snappy.parquet","deletionTimestamp":1661964655238,"dataChange":true,"extendedFileMetadata":true,"partitionValues":{"partitionpath":"asia/india/chennai"},"size":2795}} +{"commitInfo":{"timestamp":1661964655251,"operation":"WRITE","operationParameters":{"mode":"Overwrite","partitionBy":"[\"partitionpath\"]"},"readVersion":0,"isolationLevel":"Serializable","isBlindAppend":false,"operationMetrics":{"numFiles":"6","numOutputRows":"10","numOutputBytes":"17107"},"engineInfo":"Apache-Spark/3.2.2 Delta-Lake/1.1.0"}} diff --git a/tests/integration/test_storage_delta/test_table/partitionpath=americas%2Fbrazil%2Fsao_paulo/.part-00000-7212b9be-df70-42ca-831e-2ab223e7c176.c000.snappy.parquet.crc b/tests/integration/test_storage_delta/test_table/partitionpath=americas%2Fbrazil%2Fsao_paulo/.part-00000-7212b9be-df70-42ca-831e-2ab223e7c176.c000.snappy.parquet.crc new file mode 100644 index 0000000000000000000000000000000000000000..0d07fe9805f910e56d8fd1faa94b16fa1e52f59d GIT binary patch literal 32 ocmYc;N@ieSU}8{`ik6si`R}A+<}WX0E{ka&($LSF9RBwn0IB>8uK)l5 literal 0 HcmV?d00001 diff --git a/tests/integration/test_storage_delta/test_table/partitionpath=americas%2Fbrazil%2Fsao_paulo/.part-00000-df1117a8-d568-4514-b556-cd6ebe7630c9.c000.snappy.parquet.crc b/tests/integration/test_storage_delta/test_table/partitionpath=americas%2Fbrazil%2Fsao_paulo/.part-00000-df1117a8-d568-4514-b556-cd6ebe7630c9.c000.snappy.parquet.crc new file mode 100644 index 0000000000000000000000000000000000000000..1b17a91ca75be612199b8cca4cc7bdb81484cea2 GIT binary patch literal 32 ocmYc;N@ieSU}E^@y?B#jm+++B2@)xGM;lKa($LSF9RBwn0J^6QasU7T literal 0 HcmV?d00001 diff --git a/tests/integration/test_storage_delta/test_table/partitionpath=americas%2Fbrazil%2Fsao_paulo/.part-00001-3fd0374b-5fcf-42de-b929-a68f54aa1e6b.c000.snappy.parquet.crc b/tests/integration/test_storage_delta/test_table/partitionpath=americas%2Fbrazil%2Fsao_paulo/.part-00001-3fd0374b-5fcf-42de-b929-a68f54aa1e6b.c000.snappy.parquet.crc new file mode 100644 index 0000000000000000000000000000000000000000..f7f1df8479de818fab8d3ab6f8bc07dacc4763f3 GIT binary patch literal 32 qcmV+*0N?*(a$^7h00IE(#)#e7^VS5TQ*7%Xbga<4Zrk`Q1Y?EWUaC*81LFTU|% zbf2)hM;o=buYN$oMsW(id*$*wM})AE(+*n%I4kYit5!Rm%@{7{`&a(FEeXL)`dZ_Y z<IR_e|`J)J)aQv z=t(ETFl5-SRrVzKW&itIvJlG>eC*EsJIfM6K=~qm8UMZf`Q?xhrhnm39s|mJzL+T& zGuiZ5wp2;y%zQCDQOuXpf)cEj&kR|0rNqGr(f^klTNV76a5+bN&38YVu9 zaOAkGhD+9bt$DU?cK5DZO@yQ14?4GX9G#9)9=auDH7k6Bv!dbD=dYS=%h?&Q>&!O# z!6|n;j?3Tc)he!s$XZNWOTw#(Xn6$%;ZnSmuuNoficBY!Dc*D(5j5(xBDX>jq1oZc z2$7qI@CaE8;wMmYc2Jfb}XzRAA653T#n=h6SwJC!DdvqL8fkCS%(5C`x3Ki(XP-6mi!+7cUeZbD%EJu`m&?>TDqwHHY`wp

X1q{ElCQ)@NVfT3sgEK*MlM0C#Ab(me}dsbI(w_ej5R@bbU zT~qzRznJL0zF;LL6P<3mSMDZKiE7QNS2~Hw7Zwvuvq4?nJOQs8U1{}7bqm(bUcGKo zI0^f$RDw|_CKs2MQvarCH6NV98KitaBwTCG51)cNbveO7&tMb$pA*p8{^0WV=YVfN z=<&N9zPH{wNa5(mhVLCt_oXGIo^o3*-Dt<%9*t-84D@JxwA8EBEAc|XGRDdiV@1Q5 s7`Mv#Y}qJR&GE94D;2D&VZi^#Y_?p?ozT|c>-k?l^j-*|8Tgy~54oVi`2YX_ literal 0 HcmV?d00001 diff --git a/tests/integration/test_storage_delta/test_table/partitionpath=americas%2Fbrazil%2Fsao_paulo/part-00000-df1117a8-d568-4514-b556-cd6ebe7630c9.c000.snappy.parquet b/tests/integration/test_storage_delta/test_table/partitionpath=americas%2Fbrazil%2Fsao_paulo/part-00000-df1117a8-d568-4514-b556-cd6ebe7630c9.c000.snappy.parquet new file mode 100644 index 0000000000000000000000000000000000000000..a9652efacb0111d1ad3b72e982d3f5cafc7594b4 GIT binary patch literal 2795 zcmbtWU1%It6uvY2H@jVIt+|&OSa4a|C6(PFyR)0khTz|W)KWr_5=vF-?Cxx`xIgLs zC?y0W6a2hlRBPPB6VCNU*fUvN+bM*7+J1p$yD_`#T4+y*c?Xlm!{&?nT5KZDI@cr+;`O7DS zJ$Bm3Fbo;CYn6Qoe%1fswk*W51RuZq;O?@75Kz8^U%`Jbe{n4&gz2{&$`e4D&lfZ0 zVkVm&%a$tXoS84CCyMz}dfc$`#X@ePFjgJc9+fp!VFec;mNkNq<7R~hH-myZ!i%e^ z!a@j9SXqIALD9|132p{OH!CB!85G=MuXLKqu@IoJn!$bs1^0=e>ZA@8=F-N9f~TQ; zNQ;qK53Ni1f@|E9JnNS1!M@LfyFPs0jot8j)|CKWk*FDTK06)lEST+AFLuiFR>Q={ z5ssXc)o{t0t2Hmy&F=nntBG(F{6XioPNK6h%0st=tY(F8a8@*&`utV1Z81WA>IO+3#ZQLK{H2)-d9axXySq(a^e zj=+3NscNe|r<)5VOd;JKNIHCJJGE9*4H$Y>&m#5IKt$(KU5D9qsb_WdcI$P$VRg-l z*)`Q4{fmk2d5^N>poBz0yfczPOlZnhomm<_UP+=t`?ss#~yb_Ud($ z!b#X~r4o!fF}b+3l=?SCtNHL0&LHLcA>mqcZuk`3smlotdj^}}|D1r%_J^0ZKL>pK zVUOSK@V)icVG2h-Hhk}Jx-Tyw^^Dtc=|(&5_Gmn#XP`&pqorQ0UWpe9mN8bI7%Lja t#JE+?XUj&pYL1tUT&ZAH4Fmo^X0zpD?v%C$U(f&gq4z=v&A{K>e*k^vziI#g literal 0 HcmV?d00001 diff --git a/tests/integration/test_storage_delta/test_table/partitionpath=americas%2Fbrazil%2Fsao_paulo/part-00001-3fd0374b-5fcf-42de-b929-a68f54aa1e6b.c000.snappy.parquet b/tests/integration/test_storage_delta/test_table/partitionpath=americas%2Fbrazil%2Fsao_paulo/part-00001-3fd0374b-5fcf-42de-b929-a68f54aa1e6b.c000.snappy.parquet new file mode 100644 index 0000000000000000000000000000000000000000..fad00b6c557f37bf65d19b8958bc6d1564f89044 GIT binary patch literal 2878 zcmbtWU1%It6uvV%yP4f4@rT@DMi+@ox+bu*Y<4!gn;5%kz#`IzQh)FVTW04cE^cd;#Lj-!*~#Xym;$IU=p*KL#RI$0%W~X zc-^ST$x#Im@5cl9^xtPs_7g;|bpbxyx&ZRol$Sm^e=gL4@+)KU=!=j2mRnohzfik$ zh2HS(<7>aO9o(Cbe)q&jx3+U{zTe!Kkba@=U;lajaxTo=J^mGg)m+Hnee5PU&e=R!8$`IhohYw60}!Euj-X!_ZRWrkR(M z86_*Hv~)sN6KPd8Rl`t{hLTOFN$CMr5_wv9SUR<5B>|z9nHCz%POV*Y$StoVdN$Z~ z;c2}9ftJu~ru6|cEt%I$>jGw{G%k41sfHxFYi~TwGQ>NHcH@V-$)_X^b(2n;H7xM> z5Tp-DVZn4(D=eP257$}ewBd7>%bc@p_#NLZzjJyqfUhwG0(hEF^;YN9%4>(K+N;K_ ziXTKcw3`)!d1IzfI$Tt1ckjz3gnPjrXjN-B+8-u3B&5eE>25%4N8E{xGu4XGiu7A~ zrhyz7*;=(_;_CH+Zd(LS3QMa|K8!=s7>@!_6z58q6*%D~;ZT&HbUV#$KLHIp{pX%S z65&~ZWEfN%eRx1<@^}x5n(YMTl0#qu0qs?hrwvR9+E2X%n}lG~B3OZm(hCwekYH!c zf?s5WWhT19MlUd7Z@+ujK;(G|UJH_9*4$%>B+e3byAKP(S`hC;(RJXYX`^LvG_A8B ziasCFc~eAG`>9vdiGCp14w9@jV36NU;7MA;2L$v1iadRlgrNP@OVA($%@842=3D}+ zd7SGnbAV3YugKF!P0-sPHLyE#Vy|+VDrOb79X2@GODE9SxrI z4%OHoY-_vlG>UEk8)r8$^tz*D%MVKEBoTQ!UWk(RQ?HUOOUW*hr;CA>67oQ-=VPJ& z#0b|IuuZ1Xa90CI>}q;>?{MrsEDCGA_%wrrzj_2N$_uCX0T@*=Q7l(x;_93V10`#7FB-5f)r&=yEGHn|h(#!LWMY0{A@*;G za_Qb7S~Jm&hYJ@ao9-uD3PBmBn|3?8C)AvqqO?EeYr}Ar6H?~;bN#e+N!;S5Z z_mu@CK5e(0JX;yGdvtI#J_20PlL>ZvX%Q literal 0 HcmV?d00001 diff --git a/tests/integration/test_storage_delta/test_table/partitionpath=americas%2Fbrazil%2Fsao_paulo/part-00001-d0760f2d-45e8-493a-8144-d0d9d0ff572c.c000.snappy.parquet b/tests/integration/test_storage_delta/test_table/partitionpath=americas%2Fbrazil%2Fsao_paulo/part-00001-d0760f2d-45e8-493a-8144-d0d9d0ff572c.c000.snappy.parquet new file mode 100644 index 0000000000000000000000000000000000000000..1e79eb2d238771c7dbcda8b6dc5308d818038e67 GIT binary patch literal 2878 zcmbtWU1%It6uvt=KEC+P z>dT*P|2Sqai%@SW2*_%s z@S0YMC5I(IydU@BbAMl0>LrM*c>peC9sqeQ$}69&tp?jreyPo$eE!j2b1TaS=W183 z(G$M)_R4Qe8~5fT-#`Art(&PZAZP^F;mPS2-jKW}ukVSFro=h`U`LU-Q#_6PDGWTD7Zeb zG1E_HM&#FBe#Q+3?xEJ3f2&vLA1H2n6snpX#p8$dy-*SCv<^ zX&FC&aBv?Z1oGNcp>(_`*Y5r8>`C|&5ZOJ zd8UCZ7@1ntG_m!1LA5MAD~8172p7UZam0@}D2ja~%%TK?f!Fi zA&GD;Kr#%fjUL>`H#xi?Mf6sJa>*vpfq?d^z|jUK1g*Pnf^9;uZ4fNNMCk?zEJ(1j zX28#T_ytd7k%?ULgxvk^UIUTqCb%s~j2d%~A(B`_)a@S3^D6=TB#Nv9CrukIi>+y$ z0a5h&2=<*KqFQ&|qE7b$!OZ~4S_208-2|?rHM~GTFQCBDTS*97cijXHLeLBnf(6zg zFq+3c{sIf=^!^GQz0?G~b=Qsl1wr37&>Qe<(VCe3Xu!Y8zw3!Ke330SlJp>jkZ-R>Na2&W20!G%?wu0j!p{TCV4d8hn@P#iC4>V-T-J!xTC^Haj;L z{WnCpbng)6OmyPmz=hIO=SQ#xr~|ND4F3$zt(J>TSNg3U?H`U0Lyz_k=Ie!`+Mmg2QbNflMkOhm)|8Q?A}P9@R-{xu eqv?_aKSq;DWi&M;F2h6kUw^33h0r1R<@^s%AmzsZ literal 0 HcmV?d00001 diff --git a/tests/integration/test_storage_delta/test_table/partitionpath=americas%2Funited_states%2Fsan_francisco/.part-00000-8dcd9986-b57d-41e5-afe4-658c02e1aeb5.c000.snappy.parquet.crc b/tests/integration/test_storage_delta/test_table/partitionpath=americas%2Funited_states%2Fsan_francisco/.part-00000-8dcd9986-b57d-41e5-afe4-658c02e1aeb5.c000.snappy.parquet.crc new file mode 100644 index 0000000000000000000000000000000000000000..ded74b8da6d88f7d8d20b2ac33a21855f6d673d4 GIT binary patch literal 32 ocmYc;N@ieSU}7i?k3TYPRdRK{e@9!*kEv_aA3vL{@}lK70K+;Cvj6}9 literal 0 HcmV?d00001 diff --git a/tests/integration/test_storage_delta/test_table/partitionpath=americas%2Funited_states%2Fsan_francisco/.part-00000-a8bac363-ee42-47f5-a37c-1539c1bb57b1.c000.snappy.parquet.crc b/tests/integration/test_storage_delta/test_table/partitionpath=americas%2Funited_states%2Fsan_francisco/.part-00000-a8bac363-ee42-47f5-a37c-1539c1bb57b1.c000.snappy.parquet.crc new file mode 100644 index 0000000000000000000000000000000000000000..e0123ca128f2bc4783823c8f5b0c4f1c0af8cf24 GIT binary patch literal 32 ocmYc;N@ieSU}AXu&+L9ypMxH6vLf$lJ>?4Z$Im9KylA-%0I>)RmjD0& literal 0 HcmV?d00001 diff --git a/tests/integration/test_storage_delta/test_table/partitionpath=americas%2Funited_states%2Fsan_francisco/.part-00001-7e34b80c-8fe9-466b-b8e2-817f80097b3b.c000.snappy.parquet.crc b/tests/integration/test_storage_delta/test_table/partitionpath=americas%2Funited_states%2Fsan_francisco/.part-00001-7e34b80c-8fe9-466b-b8e2-817f80097b3b.c000.snappy.parquet.crc new file mode 100644 index 0000000000000000000000000000000000000000..065f09eab7a820723d2a81886e88180dbe0e3b5f GIT binary patch literal 32 ocmYc;N@ieSU}A6)6~1(D_9OEblk0^KX)J3xp>Xw_!r`Om0lUc#EC2ui literal 0 HcmV?d00001 diff --git a/tests/integration/test_storage_delta/test_table/partitionpath=americas%2Funited_states%2Fsan_francisco/.part-00001-cebe56e9-0e6f-4fe8-8135-23184ffdc617.c000.snappy.parquet.crc b/tests/integration/test_storage_delta/test_table/partitionpath=americas%2Funited_states%2Fsan_francisco/.part-00001-cebe56e9-0e6f-4fe8-8135-23184ffdc617.c000.snappy.parquet.crc new file mode 100644 index 0000000000000000000000000000000000000000..8688bc7218dab15bb560ebf9a764482f98a9c33d GIT binary patch literal 32 ocmYc;N@ieSU}C8HX3}>qXQo=B?MI`lqMw>IuISI4!18eq0Kb9_ivR!s literal 0 HcmV?d00001 diff --git a/tests/integration/test_storage_delta/test_table/partitionpath=americas%2Funited_states%2Fsan_francisco/part-00000-8dcd9986-b57d-41e5-afe4-658c02e1aeb5.c000.snappy.parquet b/tests/integration/test_storage_delta/test_table/partitionpath=americas%2Funited_states%2Fsan_francisco/part-00000-8dcd9986-b57d-41e5-afe4-658c02e1aeb5.c000.snappy.parquet new file mode 100644 index 0000000000000000000000000000000000000000..83d3695feb61efb287689fdfc8bcffe140eed604 GIT binary patch literal 2966 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 NekfgsP(S=5{|8dZ|6Tw9 literal 0 HcmV?d00001 diff --git a/tests/integration/test_storage_delta/test_table/partitionpath=americas%2Funited_states%2Fsan_francisco/part-00001-7e34b80c-8fe9-466b-b8e2-817f80097b3b.c000.snappy.parquet b/tests/integration/test_storage_delta/test_table/partitionpath=americas%2Funited_states%2Fsan_francisco/part-00001-7e34b80c-8fe9-466b-b8e2-817f80097b3b.c000.snappy.parquet new file mode 100644 index 0000000000000000000000000000000000000000..0fe5f31b711527987d2a509cce139e6711fdc643 GIT binary patch literal 2878 zcmbtW|7#m%7=Q0_Nv~PkS?PP+Nstn{qs1g7xin2nF>{E_DOkjPLF>vTcd5ZN>GDO9 zQkjL7{sF@Y4vNqpg0%Z#Klo)Zs2#G==?{ujr&MKBM$@9QNbd$mJUU^I7)7>AT6NKOY$U za^-f?JiR^t$2W(Y9OqKcc|Vx@Df#p5dwaXb*OCC}!@YR%{QY~LQOsfvp{|%0khNm= z4ZRqQMifAN1oz-8e|>bRiy(6K0C-cxHPzxcUh_{@eb}b$1hV^!|5ipLUNO{pE!Y_WShdqY&O> z_;Gw;CsG3O(>DzU@W012}O-XGehK@o`|a%H9j1Sr54C|S2G?IwvaU~*kvy#Zuio=pN0cIrup}Lt?7|gWj4zt&;uOvDi97lLs zI6$y!=OCa30yC|d)0-9s%vNn|?{=#kl4!4;{xrSdPc!Z?_qCEwN$hJSohGbL;PE)5 z4@o{j!)MLa0*jBC%XQY;t-7q`JZCMdZtHB#ZSCIj;4Owg0MGLAj?%PRe0`#neodcJ z@uLWPkFa8UN}tT;Cvs}}-_M0S!X04u)T?y_J?|qp#HCHoYqmrEi5w?3E~6Ipx~JR7 zGj-&^$d*fW6IZEZHPa$+l25uUL-%^6K^}RaC{{?A6*%FHFfQ|>cBk3xC!l_-|Lk2z zBHRm*43lc36ZZ(qJbn_%S|dRrZxFmJoRu{opuH;cw1Ejhe&fEl3I&gO4SWI78Imomdd6?f4lauLCDd8!d~a zX`Ka8^!kX_og$(dcb%dxb^*b!?Idee*MkI(q*Yx&Krf)k(_2Xh8h4!p9}~sCM8h3=;O+1Wx9KCB6qnRX`LA#mSI5t-?SF z8M7q>@9k2ykQd!bC>n|)aiHNu>0B-ZW9VE(FNcbSH$zi;S=H3CD&FB{{N=Z%b^nOJ zR4!K1Wq-h*$?7?+@|C|Hxi0KkM{PbD%qUYJ2a##;dCNA44*`CJv|srD{1%`N-MF{ eke*Q#_!^By)5Ea==`y?s|LcdcZ3vCQm-9cOPvge` literal 0 HcmV?d00001 diff --git a/tests/integration/test_storage_delta/test_table/partitionpath=americas%2Funited_states%2Fsan_francisco/part-00001-cebe56e9-0e6f-4fe8-8135-23184ffdc617.c000.snappy.parquet b/tests/integration/test_storage_delta/test_table/partitionpath=americas%2Funited_states%2Fsan_francisco/part-00001-cebe56e9-0e6f-4fe8-8135-23184ffdc617.c000.snappy.parquet new file mode 100644 index 0000000000000000000000000000000000000000..b43afdfcd30b48121adaf093c9cb4a089ca3cdfe GIT binary patch literal 2879 zcmbtWZ)h837=Q0_Nv~PgS?PP+Nstn{&|xklcWIiIYUV!3{D(#69~4(E$z2VmNtZv0 zlu8*oO1}sqo8X`bLnL(lun%(zldT&JYCp-)PML~qj?$oGl_C1Y=e@i4?pz|I69Ref zectEydw$RJywBalvY%&O?z?pQ`n6wZ zgr~pRy3;Pg=7-GsQtF3He0FnGzHp8n|NZXPd4C&p^zp{(7kv}^Z$8(qcBjq?39|O-OyLB=WT4u%u0ZSxG?1GSdo!nHJq+4!ZS~M9+ig2u}+K z2u|%h1hhb4rZw|=)53t+sg3L1?UX|j-L==BrWgEa#y#f2PVy;Fg| z4(?^e?yNplC`}f%+P|O6C4_sx9xmpEHqOv0y5;FN^GqE% zFtW9(W#a1fLf*CroD`ByMxc9x(r^IrP!uO5%nF?Fif}l>kGq}bwx588oc?onA&GD= zKr&3Kjor9kSmE(wC}Ok{luHhQ0R*&HMV>Y=A!yz85;O@x(LZcf$fgPHj)9pM-gY4{_*av{$M@GODE9SxrSHr3cL ztFa3oK#?YSOV~{ez3wP!`aubuBqC483sKU#>s9ibsbm+)(^|k%LN17TeN6Np8Q}&4 zw(%4i>S*AI9ZfH94Das2qR{BUD-06;>=L*LFMP`P!>EdiV!1LE(`GanC^2)k#NfSM zEtE^5UyUg-1&RAwPE?N+V=#t})b(1dQhqBot=F`?R@205+-$h^_KY4L3s-BEdaf3Z zhK+(=%vZx>ug-=`+B7jaqXDe;>3q4KE$Z-GsuznIS&o6f9t~6I@Yw9!T=ZUua_Rmd zteNQgL%@a7ROcbs1JniBsRk3^|0p11`u^$7$?m%ERDP}M`WDMONgR1^xW1k7zBY%% zr|g!Krz-*d>A454cnH|v$RfVrn YMahjO_em$=MfhJoROmuz0zRGp0nPZ}-~a#s literal 0 HcmV?d00001 diff --git a/tests/integration/test_storage_delta/test_table/partitionpath=asia%2Findia%2Fchennai/.part-00000-714ed689-3609-424f-acd2-d2bab8e66748.c000.snappy.parquet.crc b/tests/integration/test_storage_delta/test_table/partitionpath=asia%2Findia%2Fchennai/.part-00000-714ed689-3609-424f-acd2-d2bab8e66748.c000.snappy.parquet.crc new file mode 100644 index 0000000000000000000000000000000000000000..6756a13f94b6124c35aba9fbc36430f94f6cb589 GIT binary patch literal 32 ocmYc;N@ieSU}7+nO`pM%)grPo=iZb5l`+DHH1zW(hyT3?0Hk^hod5s; literal 0 HcmV?d00001 diff --git a/tests/integration/test_storage_delta/test_table/partitionpath=asia%2Findia%2Fchennai/.part-00000-db7e2844-bba1-41e9-841b-22762fcfc509.c000.snappy.parquet.crc b/tests/integration/test_storage_delta/test_table/partitionpath=asia%2Findia%2Fchennai/.part-00000-db7e2844-bba1-41e9-841b-22762fcfc509.c000.snappy.parquet.crc new file mode 100644 index 0000000000000000000000000000000000000000..c099f0af8e41171ba95d8d9b461fa5a50bcf7024 GIT binary patch literal 32 ocmYc;N@ieSU}E5ZXt}{4&gbbjUR8ekdm`T#+%%E&Sa!ex0I07E<^TWy literal 0 HcmV?d00001 diff --git a/tests/integration/test_storage_delta/test_table/partitionpath=asia%2Findia%2Fchennai/.part-00001-a3499b25-46da-463a-9527-a3dcd269f99e.c000.snappy.parquet.crc b/tests/integration/test_storage_delta/test_table/partitionpath=asia%2Findia%2Fchennai/.part-00001-a3499b25-46da-463a-9527-a3dcd269f99e.c000.snappy.parquet.crc new file mode 100644 index 0000000000000000000000000000000000000000..73d821f134ba262e5b03a94466fc5b4e2ec13a26 GIT binary patch literal 32 ocmYc;N@ieSU}8{w^|hQKl+|V4-?W?3rjjv-H1zW(hyT3?0IAjtI{*Lx literal 0 HcmV?d00001 diff --git a/tests/integration/test_storage_delta/test_table/partitionpath=asia%2Findia%2Fchennai/.part-00001-cbd68744-0f7d-45c7-8ca0-7594340b2c66.c000.snappy.parquet.crc b/tests/integration/test_storage_delta/test_table/partitionpath=asia%2Findia%2Fchennai/.part-00001-cbd68744-0f7d-45c7-8ca0-7594340b2c66.c000.snappy.parquet.crc new file mode 100644 index 0000000000000000000000000000000000000000..48b7b139c7afe8ebc53035ed7573f8b0aac32497 GIT binary patch literal 32 ocmYc;N@ieSU}CW0e|F?5&uf>E&bnik%6)qeY3S!o4*z=(0Jf(MQ~&?~ literal 0 HcmV?d00001 diff --git a/tests/integration/test_storage_delta/test_table/partitionpath=asia%2Findia%2Fchennai/part-00000-714ed689-3609-424f-acd2-d2bab8e66748.c000.snappy.parquet b/tests/integration/test_storage_delta/test_table/partitionpath=asia%2Findia%2Fchennai/part-00000-714ed689-3609-424f-acd2-d2bab8e66748.c000.snappy.parquet new file mode 100644 index 0000000000000000000000000000000000000000..4fde2de24ac6e65d2ba8eaec23e06f3e3c4b33a4 GIT binary patch literal 2795 zcmbtWU1%d!6uvV_+9Vq*YVTzRf`qUex{?e{CQX`B{6nOy%d&`+Wp`zFGBc^c{B-l< z52X*TK8OpIMIUM@vSLL9Q9(fvk?u={r3yZ%*wsbQhf+|A5B2Avc<#*Hxpy}a(iQS> z=KOr$Ip>~xZcaY*coriZ#|eDtv)!xVIF{ja2!|0G$;$|#fV433@jK6b`|AUq+s1o) zyTLwgUp-b=-YD)+w=tZ?m*2Yb>Yaky=y4Y<3Yb=_`i$Mmgb+ZU#pm(6pZ>NZ)}ejkLLLUlQn_Mhv&G3w z$+8QXe7-oIDHU`pW1AC&iOE8_m^V%JE?HH=tl@%)WtAZ0npva4%tXN)<;_(U;X`l< zv$g^P6Gby?CzzQinpqpc%tXN)@mr@VToxR{tY;9Q>eMpUy zSzlU{@M%xEFZtR{*%wy=zPKI0r##>Fps!sD;U$Txf#?01SbN@RJ$P~IJiqW#&$dJ=#78nGh z+-bX}V7FVfJPVQ4xVoBxdmU9L;5gw>{FJavWO9t0NQI~Q&~b^NF@F@f7Mcjd4kaT* zHV)xYvO0wCN2%Fff@Z@fm<0kht6ZW4y;_lAixO-(1Zz?p$Gj4}t~v0tpCG4F+W^n6ydVNWU%g1NK`E9a_+gY((rnFX#=HSrNOKmF1}|;9+H5ExUCU`Xq#W(>XkVylu)8jF?T*%JKC9L3j$s)c zL-`@NknB7^Zzrdc?M|y}c9Q92rE1r#c5>>>Lb74hsmb3bVD-GMX183k;k(hT)eI_5 zLA;$#GU(*g!s253R)}Wf_95Iw%Htv7YGZEj6uhm=0S;P&1Mq(;pu7F{>7D0*haa^3 zPMgQpng?kdeQbE_V7yN*BITGja+!K7;mv3wt7TzE6JzCWwPq!XMOz;?C&x>=K3Raj pNjXzDD@MW8^W~yl(RKL$n9G@^{84okUeEvfp*M#SIthPs{{_TK!qNZ$ literal 0 HcmV?d00001 diff --git a/tests/integration/test_storage_delta/test_table/partitionpath=asia%2Findia%2Fchennai/part-00000-db7e2844-bba1-41e9-841b-22762fcfc509.c000.snappy.parquet b/tests/integration/test_storage_delta/test_table/partitionpath=asia%2Findia%2Fchennai/part-00000-db7e2844-bba1-41e9-841b-22762fcfc509.c000.snappy.parquet new file mode 100644 index 0000000000000000000000000000000000000000..d936d4315277401fb42f95e78be35aedfcdb7ba0 GIT binary patch literal 2794 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 literal 0 HcmV?d00001 From 616b72d665540e4748fa4b45f3321e61121d4456 Mon Sep 17 00:00:00 2001 From: Daniil Rubin Date: Fri, 2 Sep 2022 07:11:47 +0000 Subject: [PATCH 014/266] Fix bug --- src/Storages/StorageDelta.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Storages/StorageDelta.cpp b/src/Storages/StorageDelta.cpp index 05b418e4208..52f955dee0a 100644 --- a/src/Storages/StorageDelta.cpp +++ b/src/Storages/StorageDelta.cpp @@ -93,7 +93,7 @@ void JsonMetadataGetter::Init() { LOG_DEBUG(log, "Timestamp {}", timestamp); } else if (json.has("remove")) { auto path = json["remove"]["path"].getString(); - auto timestamp = json["remove"]["modificationTime"].getInt(); + auto timestamp = json["remove"]["deletionTimestamp"].getInt(); metadata.remove(path, timestamp); From a77aa6b2b0bfaf80fc8078bc29789c091cc98a8f Mon Sep 17 00:00:00 2001 From: Daniil Rubin Date: Tue, 6 Sep 2022 15:01:34 +0000 Subject: [PATCH 015/266] Remove unneeded comments, format --- src/Storages/StorageDelta.cpp | 83 ++++++++++++++++++++--------------- src/Storages/StorageDelta.h | 12 +++-- 2 files changed, 52 insertions(+), 43 deletions(-) diff --git a/src/Storages/StorageDelta.cpp b/src/Storages/StorageDelta.cpp index 52f955dee0a..5fa60206360 100644 --- a/src/Storages/StorageDelta.cpp +++ b/src/Storages/StorageDelta.cpp @@ -2,9 +2,9 @@ #include #include -#include #include #include +#include #include #include @@ -24,49 +24,52 @@ namespace ErrorCodes extern const int S3_ERROR; } -void DeltaLakeMetadata::add(const String & key, uint64_t timestamp) { +void DeltaLakeMetadata::add(const String & key, uint64_t timestamp) +{ file_update_time[key] = timestamp; } -void DeltaLakeMetadata::remove(const String & key, uint64_t /*timestamp */) { +void DeltaLakeMetadata::remove(const String & key, uint64_t /*timestamp */) +{ file_update_time.erase(key); } -std::vector DeltaLakeMetadata::ListCurrentFiles() && { +std::vector DeltaLakeMetadata::ListCurrentFiles() && +{ std::vector keys; keys.reserve(file_update_time.size()); - for (auto && [k, _] : file_update_time) { + for (auto && [k, _] : file_update_time) + { keys.push_back(k); } return keys; } -JsonMetadataGetter::JsonMetadataGetter(StorageS3::S3Configuration & configuration_, - const String & table_path_, - Poco::Logger * log_) : - base_configuration(configuration_) - , table_path(table_path_) - , metadata() - , log(log_) - { - Init(); - } +JsonMetadataGetter::JsonMetadataGetter(StorageS3::S3Configuration & configuration_, const String & table_path_, Poco::Logger * log_) + : base_configuration(configuration_), table_path(table_path_), metadata(), log(log_) +{ + Init(); +} -void JsonMetadataGetter::Init() { +void JsonMetadataGetter::Init() +{ auto keys = getJsonLogFiles(); - + // read data from every json log file - for (const String & key : keys) { + for (const String & key : keys) + { auto buf = createS3ReadBuffer(key); String json_str; size_t opening(0), closing(0); char c; - while (buf->read(c)) { + while (buf->read(c)) + { // skip all space characters for JSON to parse correctly - if (isspace(c)) { + if (isspace(c)) + { continue; } @@ -77,13 +80,14 @@ void JsonMetadataGetter::Init() { else if (c == '}') closing++; - if (opening == closing) { - + if (opening == closing) + { LOG_DEBUG(log, "JSON {}, {}", json_str, json_str.size()); JSON json(json_str); - if (json.has("add")) { + if (json.has("add")) + { auto path = json["add"]["path"].getString(); auto timestamp = json["add"]["modificationTime"].getInt(); @@ -91,7 +95,9 @@ void JsonMetadataGetter::Init() { LOG_DEBUG(log, "Path {}", path); LOG_DEBUG(log, "Timestamp {}", timestamp); - } else if (json.has("remove")) { + } + else if (json.has("remove")) + { auto path = json["remove"]["path"].getString(); auto timestamp = json["remove"]["deletionTimestamp"].getInt(); @@ -100,20 +106,18 @@ void JsonMetadataGetter::Init() { LOG_DEBUG(log, "Path {}", path); LOG_DEBUG(log, "Timestamp {}", timestamp); } - + // reset opening = 0; closing = 0; json_str.clear(); - } - } } - } -std::vector JsonMetadataGetter::getJsonLogFiles() { +std::vector JsonMetadataGetter::getJsonLogFiles() +{ std::vector keys; const auto & client = base_configuration.client; @@ -143,7 +147,7 @@ std::vector JsonMetadataGetter::getJsonLogFiles() { for (const auto & obj : result_batch) { const auto & filename = obj.GetKey(); - + if (filename.substr(filename.size() - 5) == ".json") keys.push_back(filename); } @@ -155,11 +159,17 @@ std::vector JsonMetadataGetter::getJsonLogFiles() { return keys; } -std::unique_ptr JsonMetadataGetter::createS3ReadBuffer(const String & key) { - // size_t object_size = DB::S3::getObjectSize(base_configuration.client, base_configuration.uri.bucket, key, base_configuration.uri.version_id, false); +std::unique_ptr JsonMetadataGetter::createS3ReadBuffer(const String & key) +{ // TBD: add parallel downloads - return std::make_unique(base_configuration.client, base_configuration.uri.bucket, key, base_configuration.uri.version_id, /* max single read retries */ 10, ReadSettings{}); + return std::make_unique( + base_configuration.client, + base_configuration.uri.bucket, + key, + base_configuration.uri.version_id, + /* max single read retries */ 10, + ReadSettings{}); } StorageDelta::StorageDelta( @@ -178,12 +188,13 @@ StorageDelta::StorageDelta( { StorageInMemoryMetadata storage_metadata; updateS3Configuration(context_, base_configuration); - + JsonMetadataGetter getter{base_configuration, table_path, log}; auto keys = getter.getFiles(); - for (const String & path : keys) { + for (const String & path : keys) + { LOG_DEBUG(log, "{}", path); } @@ -200,7 +211,7 @@ StorageDelta::StorageDelta( storage_metadata.setColumns(columns_); } else - storage_metadata.setColumns(columns_); + storage_metadata.setColumns(columns_); storage_metadata.setConstraints(constraints_); storage_metadata.setComment(comment); diff --git a/src/Storages/StorageDelta.h b/src/Storages/StorageDelta.h index 2aec4f815f3..4e9199a5faa 100644 --- a/src/Storages/StorageDelta.h +++ b/src/Storages/StorageDelta.h @@ -22,7 +22,8 @@ namespace DB { // class to parse json deltalake metadata and find files needed for query in table -class DeltaLakeMetadata { +class DeltaLakeMetadata +{ public: DeltaLakeMetadata() = default; @@ -38,13 +39,10 @@ private: }; // class to get deltalake log json files and read json from them -class JsonMetadataGetter +class JsonMetadataGetter { public: - JsonMetadataGetter(StorageS3::S3Configuration & configuration_, - const String & table_path_, - Poco::Logger * log_ - ); + JsonMetadataGetter(StorageS3::S3Configuration & configuration_, const String & table_path_, Poco::Logger * log_); private: void Init(); @@ -91,7 +89,7 @@ public: private: void Init(); static void updateS3Configuration(ContextPtr, StorageS3::S3Configuration &); - + private: String generateQueryFromKeys(std::vector && keys); From f75376b7f8eaafd38a3c4d0ad13b875a73d1b778 Mon Sep 17 00:00:00 2001 From: Daniil Rubin Date: Tue, 6 Sep 2022 18:05:33 +0000 Subject: [PATCH 016/266] Apply black formatter to tests --- tests/integration/test_storage_delta/test.py | 38 +++++++++---- tests/integration/test_storage_hudi/test.py | 59 ++++++++++++++------ 2 files changed, 68 insertions(+), 29 deletions(-) diff --git a/tests/integration/test_storage_delta/test.py b/tests/integration/test_storage_delta/test.py index a1cc6345619..76dab554a57 100644 --- a/tests/integration/test_storage_delta/test.py +++ b/tests/integration/test_storage_delta/test.py @@ -9,6 +9,7 @@ from helpers.test_tools import TSV SCRIPT_DIR = os.path.dirname(os.path.realpath(__file__)) + def prepare_s3_bucket(started_cluster): bucket_read_write_policy = { "Version": "2012-10-17", @@ -49,23 +50,26 @@ def prepare_s3_bucket(started_cluster): started_cluster.minio_bucket, json.dumps(bucket_read_write_policy) ) + def upload_test_table(started_cluster): bucket = started_cluster.minio_bucket for address, dirs, files in os.walk(SCRIPT_DIR + "/test_table"): - address_without_prefix = address[len(SCRIPT_DIR):] + address_without_prefix = address[len(SCRIPT_DIR) :] for name in files: - started_cluster.minio_client.fput_object(bucket, os.path.join(address_without_prefix, name), os.path.join(address, name)) + started_cluster.minio_client.fput_object( + bucket, + os.path.join(address_without_prefix, name), + os.path.join(address, name), + ) + @pytest.fixture(scope="module") def started_cluster(): try: cluster = ClickHouseCluster(__file__) - cluster.add_instance( - "main_server", - with_minio=True - ) + cluster.add_instance("main_server", with_minio=True) logging.info("Starting cluster...") cluster.start() @@ -77,10 +81,11 @@ def started_cluster(): logging.info("Test table uploaded") yield cluster - + finally: cluster.shutdown() + def run_query(instance, query, stdin=None, settings=None): # type: (ClickHouseInstance, str, object, dict) -> str @@ -99,13 +104,22 @@ def test_create_query(started_cluster): run_query(instance, create_query) + def test_select_query(started_cluster): instance = started_cluster.instances["main_server"] bucket = started_cluster.minio_bucket - columns = ['begin_lat', - 'begin_lon', 'driver', 'end_lat', 'end_lon', - 'fare', 'rider', 'ts', 'uuid'] - + columns = [ + "begin_lat", + "begin_lon", + "driver", + "end_lat", + "end_lon", + "fare", + "rider", + "ts", + "uuid", + ] + # create query in case table doesn't exist create_query = f"""CREATE TABLE IF NOT EXISTS deltalake ENGINE=DeltaLake('http://{started_cluster.minio_ip}:{started_cluster.minio_port}/{bucket}/test_table/', 'minio', 'minio123')""" @@ -115,4 +129,4 @@ def test_select_query(started_cluster): for column_name in columns: result = run_query(instance, select_query.format(column_name)).splitlines() - assert(len(result) > 0) + assert len(result) > 0 diff --git a/tests/integration/test_storage_hudi/test.py b/tests/integration/test_storage_hudi/test.py index c9415e28151..549421afd89 100644 --- a/tests/integration/test_storage_hudi/test.py +++ b/tests/integration/test_storage_hudi/test.py @@ -9,6 +9,7 @@ from helpers.test_tools import TSV SCRIPT_DIR = os.path.dirname(os.path.realpath(__file__)) + def prepare_s3_bucket(started_cluster): bucket_read_write_policy = { "Version": "2012-10-17", @@ -49,23 +50,26 @@ def prepare_s3_bucket(started_cluster): started_cluster.minio_bucket, json.dumps(bucket_read_write_policy) ) + def upload_test_table(started_cluster): bucket = started_cluster.minio_bucket for address, dirs, files in os.walk(SCRIPT_DIR + "/test_table"): - address_without_prefix = address[len(SCRIPT_DIR):] + address_without_prefix = address[len(SCRIPT_DIR) :] for name in files: - started_cluster.minio_client.fput_object(bucket, os.path.join(address_without_prefix, name), os.path.join(address, name)) + started_cluster.minio_client.fput_object( + bucket, + os.path.join(address_without_prefix, name), + os.path.join(address, name), + ) + @pytest.fixture(scope="module") def started_cluster(): try: cluster = ClickHouseCluster(__file__) - cluster.add_instance( - "main_server", - with_minio=True - ) + cluster.add_instance("main_server", with_minio=True) logging.info("Starting cluster...") cluster.start() @@ -77,10 +81,11 @@ def started_cluster(): logging.info("Test table uploaded") yield cluster - + finally: cluster.shutdown() + def run_query(instance, query, stdin=None, settings=None): # type: (ClickHouseInstance, str, object, dict) -> str @@ -99,14 +104,28 @@ def test_create_query(started_cluster): run_query(instance, create_query) + def test_select_query(started_cluster): instance = started_cluster.instances["main_server"] bucket = started_cluster.minio_bucket - columns = ['_hoodie_commit_time', '_hoodie_commit_seqno', '_hoodie_record_key', - '_hoodie_partition_path', '_hoodie_file_name', 'begin_lat', - 'begin_lon', 'driver', 'end_lat', 'end_lon', - 'fare', 'partitionpath', 'rider', 'ts', 'uuid'] - + columns = [ + "_hoodie_commit_time", + "_hoodie_commit_seqno", + "_hoodie_record_key", + "_hoodie_partition_path", + "_hoodie_file_name", + "begin_lat", + "begin_lon", + "driver", + "end_lat", + "end_lon", + "fare", + "partitionpath", + "rider", + "ts", + "uuid", + ] + # create query in case table doesn't exist create_query = f"""CREATE TABLE IF NOT EXISTS hudi ENGINE=Hudi('http://{started_cluster.minio_ip}:{started_cluster.minio_port}/{bucket}/test_table/', 'minio', 'minio123')""" @@ -116,11 +135,17 @@ def test_select_query(started_cluster): for column_name in columns: result = run_query(instance, select_query.format(column_name)).splitlines() - assert(len(result) > 0) + assert len(result) > 0 - # test if all partition paths is presented in result - distinct_select_query = "SELECT DISTINCT partitionpath FROM hudi ORDER BY partitionpath FORMAT TSV" + # test if all partition paths is presented in result + distinct_select_query = ( + "SELECT DISTINCT partitionpath FROM hudi ORDER BY partitionpath FORMAT TSV" + ) result = run_query(instance, distinct_select_query) - expected = ['americas/brazil/sao_paulo', 'americas/united_states/san_francisco', 'asia/india/chennai'] + expected = [ + "americas/brazil/sao_paulo", + "americas/united_states/san_francisco", + "asia/india/chennai", + ] - assert TSV(result) == TSV(expected) \ No newline at end of file + assert TSV(result) == TSV(expected) From 07d12338ca7acfb828737c0744fb01ceeb5a6233 Mon Sep 17 00:00:00 2001 From: Daniil Rubin Date: Tue, 6 Sep 2022 18:12:51 +0000 Subject: [PATCH 017/266] Fix ErrorCodes style --- src/Storages/StorageDelta.cpp | 2 +- src/Storages/StorageHudi.cpp | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Storages/StorageDelta.cpp b/src/Storages/StorageDelta.cpp index 5fa60206360..518c25c2dff 100644 --- a/src/Storages/StorageDelta.cpp +++ b/src/Storages/StorageDelta.cpp @@ -20,8 +20,8 @@ namespace DB namespace ErrorCodes { - extern const int NUMBER_OF_ARGUMENTS_DOESNT_MATCH; extern const int S3_ERROR; + extern const int BAD_ARGUMENTS; } void DeltaLakeMetadata::add(const String & key, uint64_t timestamp) diff --git a/src/Storages/StorageHudi.cpp b/src/Storages/StorageHudi.cpp index b4eba258d0e..f3e16d2969a 100644 --- a/src/Storages/StorageHudi.cpp +++ b/src/Storages/StorageHudi.cpp @@ -17,7 +17,7 @@ namespace DB namespace ErrorCodes { - extern const int NUMBER_OF_ARGUMENTS_DOESNT_MATCH; + extern const int BAD_ARGUMENTS; extern const int S3_ERROR; } From ba08366af18bd89ea47e15146028b0c8f280f928 Mon Sep 17 00:00:00 2001 From: Daniil Rubin Date: Wed, 7 Sep 2022 07:16:32 +0000 Subject: [PATCH 018/266] Fix fasttest build --- src/Storages/StorageDelta.cpp | 7 ++++++- src/Storages/StorageDelta.h | 4 ++++ src/Storages/StorageHudi.cpp | 7 ++++++- src/Storages/StorageHudi.h | 4 ++++ 4 files changed, 20 insertions(+), 2 deletions(-) diff --git a/src/Storages/StorageDelta.cpp b/src/Storages/StorageDelta.cpp index 518c25c2dff..9f53d0edba8 100644 --- a/src/Storages/StorageDelta.cpp +++ b/src/Storages/StorageDelta.cpp @@ -1,5 +1,8 @@ -#include #include + +#if USE_AWS_S3 + +#include #include #include @@ -346,3 +349,5 @@ void registerStorageDelta(StorageFactory & factory) } } + +#endif diff --git a/src/Storages/StorageDelta.h b/src/Storages/StorageDelta.h index 4e9199a5faa..cf9cc737526 100644 --- a/src/Storages/StorageDelta.h +++ b/src/Storages/StorageDelta.h @@ -2,6 +2,8 @@ #include "config_core.h" +#if USE_AWS_S3 + #include #include @@ -102,3 +104,5 @@ private: }; } + +#endif diff --git a/src/Storages/StorageHudi.cpp b/src/Storages/StorageHudi.cpp index f3e16d2969a..6162f2ba4dc 100644 --- a/src/Storages/StorageHudi.cpp +++ b/src/Storages/StorageHudi.cpp @@ -1,5 +1,8 @@ -#include #include + +#if USE_AWS_S3 + +#include #include #include @@ -280,3 +283,5 @@ void registerStorageHudi(StorageFactory & factory) } } + +#endif diff --git a/src/Storages/StorageHudi.h b/src/Storages/StorageHudi.h index 47dff1c2a7b..5e1aae81182 100644 --- a/src/Storages/StorageHudi.h +++ b/src/Storages/StorageHudi.h @@ -2,6 +2,8 @@ #include "config_core.h" +#if USE_AWS_S3 + #include #include @@ -57,3 +59,5 @@ private: }; } + +#endif From 152da1a034b79798d996b6e412f20f3bb9537d2f Mon Sep 17 00:00:00 2001 From: Daniil Rubin Date: Wed, 7 Sep 2022 08:13:08 +0000 Subject: [PATCH 019/266] Fix build --- src/Storages/registerStorages.cpp | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/Storages/registerStorages.cpp b/src/Storages/registerStorages.cpp index c0d153a5efa..c64c2e8d2cd 100644 --- a/src/Storages/registerStorages.cpp +++ b/src/Storages/registerStorages.cpp @@ -34,6 +34,8 @@ void registerStorageMeiliSearch(StorageFactory& factory); #if USE_AWS_S3 void registerStorageS3(StorageFactory & factory); void registerStorageCOS(StorageFactory & factory); +void registerStorageHudi(StorageFactory & factory); +void registerStorageDelta(StorageFactory & factory); #endif #if USE_HDFS @@ -88,8 +90,6 @@ void registerStorageFileLog(StorageFactory & factory); void registerStorageSQLite(StorageFactory & factory); #endif -void registerStorageHudi(StorageFactory & factory); -void registerStorageDelta(StorageFactory & factory); void registerStorages() { @@ -121,6 +121,8 @@ void registerStorages() #if USE_AWS_S3 registerStorageS3(factory); registerStorageCOS(factory); + registerStorageHudi(factory); + registerStorageDelta(factory); #endif #if USE_HDFS @@ -174,8 +176,6 @@ void registerStorages() registerStorageSQLite(factory); #endif - registerStorageHudi(factory); - registerStorageDelta(factory); } } From 3f9022bc7f2d326a41382c23fd4709be6bc99c27 Mon Sep 17 00:00:00 2001 From: Daniil Rubin Date: Wed, 7 Sep 2022 10:48:11 +0000 Subject: [PATCH 020/266] Fix createClientConfiguration arguments --- src/Storages/StorageDelta.cpp | 3 ++- src/Storages/StorageHudi.cpp | 3 ++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/src/Storages/StorageDelta.cpp b/src/Storages/StorageDelta.cpp index 9f53d0edba8..c2d421c827f 100644 --- a/src/Storages/StorageDelta.cpp +++ b/src/Storages/StorageDelta.cpp @@ -276,7 +276,8 @@ void StorageDelta::updateS3Configuration(ContextPtr ctx, StorageS3::S3Configurat settings.auth_settings.region, ctx->getRemoteHostFilter(), ctx->getGlobalContext()->getSettingsRef().s3_max_redirects, - ctx->getGlobalContext()->getSettingsRef().enable_s3_requests_logging); + ctx->getGlobalContext()->getSettingsRef().enable_s3_requests_logging, + /* for_disk_s3 = */ false); client_configuration.endpointOverride = upd.uri.endpoint; client_configuration.maxConnections = upd.rw_settings.max_connections; diff --git a/src/Storages/StorageHudi.cpp b/src/Storages/StorageHudi.cpp index 6162f2ba4dc..9b6207338eb 100644 --- a/src/Storages/StorageHudi.cpp +++ b/src/Storages/StorageHudi.cpp @@ -118,7 +118,8 @@ void StorageHudi::updateS3Configuration(ContextPtr ctx, StorageS3::S3Configurati settings.auth_settings.region, ctx->getRemoteHostFilter(), ctx->getGlobalContext()->getSettingsRef().s3_max_redirects, - ctx->getGlobalContext()->getSettingsRef().enable_s3_requests_logging); + ctx->getGlobalContext()->getSettingsRef().enable_s3_requests_logging, + /* for_disk_s3 = */ false); client_configuration.endpointOverride = upd.uri.endpoint; client_configuration.maxConnections = upd.rw_settings.max_connections; From 0a1734bb69f85ae9ccc009b720567efbcdf56301 Mon Sep 17 00:00:00 2001 From: Daniil Rubin Date: Mon, 19 Sep 2022 15:23:07 +0000 Subject: [PATCH 021/266] Fix clang-tidy errors --- src/Storages/StorageDelta.cpp | 10 +++++----- src/Storages/StorageDelta.h | 2 +- src/Storages/StorageHudi.h | 2 +- 3 files changed, 7 insertions(+), 7 deletions(-) diff --git a/src/Storages/StorageDelta.cpp b/src/Storages/StorageDelta.cpp index c2d421c827f..46db33279ab 100644 --- a/src/Storages/StorageDelta.cpp +++ b/src/Storages/StorageDelta.cpp @@ -27,14 +27,14 @@ namespace ErrorCodes extern const int BAD_ARGUMENTS; } -void DeltaLakeMetadata::add(const String & key, uint64_t timestamp) +void DeltaLakeMetadata::add(const String & filename, uint64_t timestamp) { - file_update_time[key] = timestamp; + file_update_time[filename] = timestamp; } -void DeltaLakeMetadata::remove(const String & key, uint64_t /*timestamp */) +void DeltaLakeMetadata::remove(const String & filename, uint64_t /*timestamp */) { - file_update_time.erase(key); + file_update_time.erase(filename); } std::vector DeltaLakeMetadata::ListCurrentFiles() && @@ -51,7 +51,7 @@ std::vector DeltaLakeMetadata::ListCurrentFiles() && } JsonMetadataGetter::JsonMetadataGetter(StorageS3::S3Configuration & configuration_, const String & table_path_, Poco::Logger * log_) - : base_configuration(configuration_), table_path(table_path_), metadata(), log(log_) + : base_configuration(configuration_), table_path(table_path_), log(log_) { Init(); } diff --git a/src/Storages/StorageDelta.h b/src/Storages/StorageDelta.h index cf9cc737526..f4547666c8c 100644 --- a/src/Storages/StorageDelta.h +++ b/src/Storages/StorageDelta.h @@ -94,7 +94,7 @@ private: private: - String generateQueryFromKeys(std::vector && keys); + static String generateQueryFromKeys(std::vector && keys); private: StorageS3::S3Configuration base_configuration; diff --git a/src/Storages/StorageHudi.h b/src/Storages/StorageHudi.h index 5e1aae81182..ceb12f5f550 100644 --- a/src/Storages/StorageHudi.h +++ b/src/Storages/StorageHudi.h @@ -49,7 +49,7 @@ private: private: std::vector getKeysFromS3(); - std::string generateQueryFromKeys(std::vector && keys); + static std::string generateQueryFromKeys(std::vector && keys); private: StorageS3::S3Configuration base_configuration; From 3fb26aefa39eda4d1f23b5b189699ea264fa8af1 Mon Sep 17 00:00:00 2001 From: Daniil Rubin Date: Tue, 20 Sep 2022 10:24:47 +0000 Subject: [PATCH 022/266] Remove unneeded logging, better --- src/Storages/StorageDelta.cpp | 39 ++++++++++------------------------- src/Storages/StorageDelta.h | 12 ++--------- 2 files changed, 13 insertions(+), 38 deletions(-) diff --git a/src/Storages/StorageDelta.cpp b/src/Storages/StorageDelta.cpp index 46db33279ab..a79762e29db 100644 --- a/src/Storages/StorageDelta.cpp +++ b/src/Storages/StorageDelta.cpp @@ -18,6 +18,9 @@ #include +#include +#include + namespace DB { @@ -25,6 +28,7 @@ namespace ErrorCodes { extern const int S3_ERROR; extern const int BAD_ARGUMENTS; + extern const int INCORRECT_DATA; } void DeltaLakeMetadata::add(const String & filename, uint64_t timestamp) @@ -34,7 +38,9 @@ void DeltaLakeMetadata::add(const String & filename, uint64_t timestamp) void DeltaLakeMetadata::remove(const String & filename, uint64_t /*timestamp */) { - file_update_time.erase(filename); + bool erase = file_update_time.erase(filename); + if (!erase) + throw Exception(ErrorCodes::INCORRECT_DATA, "Invalid table metadata, tried to remove {} before adding it", filename); } std::vector DeltaLakeMetadata::ListCurrentFiles() && @@ -95,9 +101,6 @@ void JsonMetadataGetter::Init() auto timestamp = json["add"]["modificationTime"].getInt(); metadata.add(path, timestamp); - - LOG_DEBUG(log, "Path {}", path); - LOG_DEBUG(log, "Timestamp {}", timestamp); } else if (json.has("remove")) { @@ -105,9 +108,6 @@ void JsonMetadataGetter::Init() auto timestamp = json["remove"]["deletionTimestamp"].getInt(); metadata.remove(path, timestamp); - - LOG_DEBUG(log, "Path {}", path); - LOG_DEBUG(log, "Timestamp {}", timestamp); } // reset @@ -132,7 +132,7 @@ std::vector JsonMetadataGetter::getJsonLogFiles() const auto bucket{base_configuration.uri.bucket}; request.SetBucket(bucket); - request.SetPrefix(table_path + "_delta_log"); + request.SetPrefix(std::filesystem::path(table_path) / "_delta_log"); while (!is_finished) { @@ -151,7 +151,7 @@ std::vector JsonMetadataGetter::getJsonLogFiles() { const auto & filename = obj.GetKey(); - if (filename.substr(filename.size() - 5) == ".json") + if (std::filesystem::path(filename).extension() == ".json") keys.push_back(filename); } @@ -164,7 +164,6 @@ std::vector JsonMetadataGetter::getJsonLogFiles() std::unique_ptr JsonMetadataGetter::createS3ReadBuffer(const String & key) { - // TBD: add parallel downloads return std::make_unique( base_configuration.client, @@ -196,11 +195,6 @@ StorageDelta::StorageDelta( auto keys = getter.getFiles(); - for (const String & path : keys) - { - LOG_DEBUG(log, "{}", path); - } - auto new_uri = base_configuration.uri.uri.toString() + generateQueryFromKeys(std::move(keys)); LOG_DEBUG(log, "New uri: {}", new_uri); @@ -225,7 +219,7 @@ StorageDelta::StorageDelta( access_key_, secret_access_key_, table_id_, - String("Parquet"), // format name + "Parquet", // format name base_configuration.rw_settings, columns_, constraints_, @@ -297,18 +291,7 @@ void StorageDelta::updateS3Configuration(ContextPtr ctx, StorageS3::S3Configurat String StorageDelta::generateQueryFromKeys(std::vector && keys) { - String new_query; - - for (auto && key : keys) - { - if (!new_query.empty()) - { - new_query += ","; - } - new_query += key; - } - new_query = "{" + new_query + "}"; - + std::string new_query = fmt::format("{{{}}}", fmt::join(keys, ",")); return new_query; } diff --git a/src/Storages/StorageDelta.h b/src/Storages/StorageDelta.h index f4547666c8c..f7fa4120495 100644 --- a/src/Storages/StorageDelta.h +++ b/src/Storages/StorageDelta.h @@ -29,11 +29,9 @@ class DeltaLakeMetadata public: DeltaLakeMetadata() = default; -public: void add(const String & filename, uint64_t timestamp); void remove(const String & filename, uint64_t timestamp); -public: std::vector ListCurrentFiles() &&; private: @@ -46,18 +44,15 @@ class JsonMetadataGetter public: JsonMetadataGetter(StorageS3::S3Configuration & configuration_, const String & table_path_, Poco::Logger * log_); + std::vector getFiles() { return std::move(metadata).ListCurrentFiles(); } + private: void Init(); std::vector getJsonLogFiles(); -private: std::unique_ptr createS3ReadBuffer(const String & key); -public: - std::vector getFiles() { return std::move(metadata).ListCurrentFiles(); } - -private: StorageS3::S3Configuration base_configuration; String table_path; DeltaLakeMetadata metadata; @@ -92,11 +87,8 @@ private: void Init(); static void updateS3Configuration(ContextPtr, StorageS3::S3Configuration &); - -private: static String generateQueryFromKeys(std::vector && keys); -private: StorageS3::S3Configuration base_configuration; std::shared_ptr s3engine; Poco::Logger * log; From 745331a3f3991440d4a87554fac3c3f8b26add75 Mon Sep 17 00:00:00 2001 From: Daniil Rubin Date: Tue, 20 Sep 2022 11:43:20 +0000 Subject: [PATCH 023/266] Add format argument with default value to storage --- src/Storages/StorageDelta.cpp | 33 +++++++++++++++++++-------------- src/Storages/StorageDelta.h | 1 + 2 files changed, 20 insertions(+), 14 deletions(-) diff --git a/src/Storages/StorageDelta.cpp b/src/Storages/StorageDelta.cpp index a79762e29db..9b36b234fc5 100644 --- a/src/Storages/StorageDelta.cpp +++ b/src/Storages/StorageDelta.cpp @@ -27,7 +27,7 @@ namespace DB namespace ErrorCodes { extern const int S3_ERROR; - extern const int BAD_ARGUMENTS; + extern const int NUMBER_OF_ARGUMENTS_DOESNT_MATCH; extern const int INCORRECT_DATA; } @@ -179,6 +179,7 @@ StorageDelta::StorageDelta( const String & access_key_, const String & secret_access_key_, const StorageID & table_id_, + const String & format_name_, ColumnsDescription columns_, const ConstraintsDescription & constraints_, const String & comment, @@ -204,7 +205,7 @@ StorageDelta::StorageDelta( if (columns_.empty()) { columns_ - = StorageS3::getTableStructureFromData(String("Parquet"), s3_uri, access_key_, secret_access_key_, "", false, {}, context_); + = StorageS3::getTableStructureFromData(format_name_, s3_uri, access_key_, secret_access_key_, "", false, {}, context_); storage_metadata.setColumns(columns_); } else @@ -219,7 +220,7 @@ StorageDelta::StorageDelta( access_key_, secret_access_key_, table_id_, - "Parquet", // format name + format_name_, base_configuration.rw_settings, columns_, constraints_, @@ -303,23 +304,27 @@ void registerStorageDelta(StorageFactory & factory) [](const StorageFactory::Arguments & args) { auto & engine_args = args.engine_args; - if (engine_args.empty()) - throw Exception(ErrorCodes::BAD_ARGUMENTS, "External data source must have arguments"); + if (engine_args.empty() || engine_args.size() < 3) + throw Exception(ErrorCodes::NUMBER_OF_ARGUMENTS_DOESNT_MATCH, "Storage DeltaLake requires 3 to 4 arguments: table_url, access_key, secret_access_key, [format]"); - auto configuration = StorageS3::getConfiguration(engine_args, args.getLocalContext()); + + String table_url = checkAndGetLiteralArgument(engine_args[0], "url"); + String access_key_id = checkAndGetLiteralArgument(engine_args[1], "access_key_id"); + String secret_access_key = checkAndGetLiteralArgument(engine_args[2], "secret_access_key"); + + String format = "Parquet"; + if (engine_args.size() == 4) { + format = checkAndGetLiteralArgument(engine_args[3], "format"); + } - configuration.url = checkAndGetLiteralArgument(engine_args[0], "url"); - configuration.auth_settings.access_key_id = checkAndGetLiteralArgument(engine_args[1], "access_key_id"); - configuration.auth_settings.secret_access_key = checkAndGetLiteralArgument(engine_args[2], "secret_access_key"); - - - S3::URI s3_uri(Poco::URI(configuration.url)); + auto s3_uri = S3::URI(Poco::URI(table_url)); return std::make_shared( s3_uri, - configuration.auth_settings.access_key_id, - configuration.auth_settings.secret_access_key, + access_key_id, + secret_access_key, args.table_id, + format, args.columns, args.constraints, args.comment, diff --git a/src/Storages/StorageDelta.h b/src/Storages/StorageDelta.h index f7fa4120495..010fc53546d 100644 --- a/src/Storages/StorageDelta.h +++ b/src/Storages/StorageDelta.h @@ -67,6 +67,7 @@ public: const String & access_key_, const String & secret_access_key_, const StorageID & table_id_, + const String & format_name_, ColumnsDescription columns_, const ConstraintsDescription & constraints_, const String & comment, From 8fe4485ee8ddc049cff3775a7b9f6293a4a457a6 Mon Sep 17 00:00:00 2001 From: Daniil Rubin Date: Tue, 20 Sep 2022 14:16:27 +0000 Subject: [PATCH 024/266] Change JSON parsing --- src/Storages/StorageDelta.cpp | 97 ++++++++++++++++++++--------------- src/Storages/StorageDelta.h | 9 +++- 2 files changed, 64 insertions(+), 42 deletions(-) diff --git a/src/Storages/StorageDelta.cpp b/src/Storages/StorageDelta.cpp index 9b36b234fc5..ec0f233f9ed 100644 --- a/src/Storages/StorageDelta.cpp +++ b/src/Storages/StorageDelta.cpp @@ -70,51 +70,18 @@ void JsonMetadataGetter::Init() for (const String & key : keys) { auto buf = createS3ReadBuffer(key); - String json_str; - size_t opening(0), closing(0); - char c; - while (buf->read(c)) + while (!buf->eof()) { - // skip all space characters for JSON to parse correctly - if (isspace(c)) - { + String json_str = readJSONStringFromBuffer(buf); + + if (json_str.empty()) { continue; } - json_str.push_back(c); + const JSON json(json_str); - if (c == '{') - opening++; - else if (c == '}') - closing++; - - if (opening == closing) - { - LOG_DEBUG(log, "JSON {}, {}", json_str, json_str.size()); - - JSON json(json_str); - - if (json.has("add")) - { - auto path = json["add"]["path"].getString(); - auto timestamp = json["add"]["modificationTime"].getInt(); - - metadata.add(path, timestamp); - } - else if (json.has("remove")) - { - auto path = json["remove"]["path"].getString(); - auto timestamp = json["remove"]["deletionTimestamp"].getInt(); - - metadata.remove(path, timestamp); - } - - // reset - opening = 0; - closing = 0; - json_str.clear(); - } + handleJSON(json); } } } @@ -162,10 +129,10 @@ std::vector JsonMetadataGetter::getJsonLogFiles() return keys; } -std::unique_ptr JsonMetadataGetter::createS3ReadBuffer(const String & key) +std::shared_ptr JsonMetadataGetter::createS3ReadBuffer(const String & key) { // TBD: add parallel downloads - return std::make_unique( + return std::make_shared( base_configuration.client, base_configuration.uri.bucket, key, @@ -174,6 +141,54 @@ std::unique_ptr JsonMetadataGetter::createS3ReadBuffer(const String ReadSettings{}); } +String JsonMetadataGetter::readJSONStringFromBuffer(std::shared_ptr buf) { + String json_str; + + int32_t opening(0), closing(0); + + do { + char c; + + if (!buf->read(c)) + return json_str; + + // skip all space characters for JSON to parse correctly + if (isspace(c)) + { + continue; + } + + json_str.push_back(c); + + if (c == '{') + opening++; + else if (c == '}') + closing++; + + } while (opening != closing || opening == 0); + + LOG_DEBUG(log, "JSON {}", json_str); + + return json_str; +} + +void JsonMetadataGetter::handleJSON(const JSON & json) { + if (json.has("add")) + { + auto path = json["add"]["path"].getString(); + auto timestamp = json["add"]["modificationTime"].getInt(); + + metadata.add(path, timestamp); + } + else if (json.has("remove")) + { + auto path = json["remove"]["path"].getString(); + auto timestamp = json["remove"]["deletionTimestamp"].getInt(); + + metadata.remove(path, timestamp); + } +} + StorageDelta::StorageDelta( const S3::URI & uri_, const String & access_key_, diff --git a/src/Storages/StorageDelta.h b/src/Storages/StorageDelta.h index 010fc53546d..1b9ee960080 100644 --- a/src/Storages/StorageDelta.h +++ b/src/Storages/StorageDelta.h @@ -51,7 +51,14 @@ private: std::vector getJsonLogFiles(); - std::unique_ptr createS3ReadBuffer(const String & key); + std::shared_ptr createS3ReadBuffer(const String & key); + + /* every metadata file contains many jsons + this function reads one json from buffer + */ + String readJSONStringFromBuffer(std::shared_ptr buf); + + void handleJSON(const JSON & json); StorageS3::S3Configuration base_configuration; String table_path; From 3f7779e874ac27ab772dafdd36a923b32700db3c Mon Sep 17 00:00:00 2001 From: Daniil Rubin Date: Wed, 21 Sep 2022 16:14:51 +0000 Subject: [PATCH 025/266] Rename method, change parsing method --- src/Storages/StorageDelta.cpp | 50 +++++++++-------------------------- src/Storages/StorageDelta.h | 7 +---- 2 files changed, 14 insertions(+), 43 deletions(-) diff --git a/src/Storages/StorageDelta.cpp b/src/Storages/StorageDelta.cpp index ec0f233f9ed..5818e770a37 100644 --- a/src/Storages/StorageDelta.cpp +++ b/src/Storages/StorageDelta.cpp @@ -6,6 +6,7 @@ #include #include +#include #include #include @@ -31,7 +32,7 @@ namespace ErrorCodes extern const int INCORRECT_DATA; } -void DeltaLakeMetadata::add(const String & filename, uint64_t timestamp) +void DeltaLakeMetadata::setLastModifiedTime(const String & filename, uint64_t timestamp) { file_update_time[filename] = timestamp; } @@ -72,12 +73,18 @@ void JsonMetadataGetter::Init() auto buf = createS3ReadBuffer(key); while (!buf->eof()) - { - String json_str = readJSONStringFromBuffer(buf); + { + // may be some invalid characters before json + char c; + while ( buf->peek(c) && c != '{') buf->ignore(); + if (buf->eof()) + break; + + String json_str; + readJSONObjectPossiblyInvalid(json_str, *buf); - if (json_str.empty()) { + if (json_str.empty()) continue; - } const JSON json(json_str); @@ -141,44 +148,13 @@ std::shared_ptr JsonMetadataGetter::createS3ReadBuffer(const String ReadSettings{}); } -String JsonMetadataGetter::readJSONStringFromBuffer(std::shared_ptr buf) { - String json_str; - - int32_t opening(0), closing(0); - - do { - char c; - - if (!buf->read(c)) - return json_str; - - // skip all space characters for JSON to parse correctly - if (isspace(c)) - { - continue; - } - - json_str.push_back(c); - - if (c == '{') - opening++; - else if (c == '}') - closing++; - - } while (opening != closing || opening == 0); - - LOG_DEBUG(log, "JSON {}", json_str); - - return json_str; -} - void JsonMetadataGetter::handleJSON(const JSON & json) { if (json.has("add")) { auto path = json["add"]["path"].getString(); auto timestamp = json["add"]["modificationTime"].getInt(); - metadata.add(path, timestamp); + metadata.setLastModifiedTime(path, timestamp); } else if (json.has("remove")) { diff --git a/src/Storages/StorageDelta.h b/src/Storages/StorageDelta.h index 1b9ee960080..542d0b7c4c0 100644 --- a/src/Storages/StorageDelta.h +++ b/src/Storages/StorageDelta.h @@ -29,7 +29,7 @@ class DeltaLakeMetadata public: DeltaLakeMetadata() = default; - void add(const String & filename, uint64_t timestamp); + void setLastModifiedTime(const String & filename, uint64_t timestamp); void remove(const String & filename, uint64_t timestamp); std::vector ListCurrentFiles() &&; @@ -52,11 +52,6 @@ private: std::vector getJsonLogFiles(); std::shared_ptr createS3ReadBuffer(const String & key); - - /* every metadata file contains many jsons - this function reads one json from buffer - */ - String readJSONStringFromBuffer(std::shared_ptr buf); void handleJSON(const JSON & json); From 4d1abdc80891a468869007c19120d324c877e499 Mon Sep 17 00:00:00 2001 From: Daniil Rubin Date: Wed, 21 Sep 2022 16:54:02 +0000 Subject: [PATCH 026/266] Remove unneeded logging, add comment, better --- src/Storages/StorageDelta.cpp | 6 +++--- src/Storages/StorageDelta.h | 3 +-- src/Storages/StorageHudi.cpp | 7 ++----- 3 files changed, 6 insertions(+), 10 deletions(-) diff --git a/src/Storages/StorageDelta.cpp b/src/Storages/StorageDelta.cpp index 5818e770a37..951337f88c3 100644 --- a/src/Storages/StorageDelta.cpp +++ b/src/Storages/StorageDelta.cpp @@ -57,8 +57,8 @@ std::vector DeltaLakeMetadata::ListCurrentFiles() && return keys; } -JsonMetadataGetter::JsonMetadataGetter(StorageS3::S3Configuration & configuration_, const String & table_path_, Poco::Logger * log_) - : base_configuration(configuration_), table_path(table_path_), log(log_) +JsonMetadataGetter::JsonMetadataGetter(StorageS3::S3Configuration & configuration_, const String & table_path_) + : base_configuration(configuration_), table_path(table_path_) { Init(); } @@ -183,7 +183,7 @@ StorageDelta::StorageDelta( StorageInMemoryMetadata storage_metadata; updateS3Configuration(context_, base_configuration); - JsonMetadataGetter getter{base_configuration, table_path, log}; + JsonMetadataGetter getter{base_configuration, table_path}; auto keys = getter.getFiles(); diff --git a/src/Storages/StorageDelta.h b/src/Storages/StorageDelta.h index 542d0b7c4c0..fb6e19b7488 100644 --- a/src/Storages/StorageDelta.h +++ b/src/Storages/StorageDelta.h @@ -42,7 +42,7 @@ private: class JsonMetadataGetter { public: - JsonMetadataGetter(StorageS3::S3Configuration & configuration_, const String & table_path_, Poco::Logger * log_); + JsonMetadataGetter(StorageS3::S3Configuration & configuration_, const String & table_path_); std::vector getFiles() { return std::move(metadata).ListCurrentFiles(); } @@ -58,7 +58,6 @@ private: StorageS3::S3Configuration base_configuration; String table_path; DeltaLakeMetadata metadata; - Poco::Logger * log; }; class StorageDelta : public IStorage diff --git a/src/Storages/StorageHudi.cpp b/src/Storages/StorageHudi.cpp index 9b6207338eb..55f7e4b9c10 100644 --- a/src/Storages/StorageHudi.cpp +++ b/src/Storages/StorageHudi.cpp @@ -186,11 +186,7 @@ std::string StorageHudi::generateQueryFromKeys(std::vector && keys) keys, [](const std::string & s) { - if (s.size() >= 8) - { - return s.substr(s.size() - 8) != ".parquet"; - } - return true; + return std::filesystem::path(s).extension() != "parquet"; }); // for each partition path take only latest parquet file @@ -210,6 +206,7 @@ std::string StorageHudi::generateQueryFromKeys(std::vector && keys) path = key.substr(0, slash); } + // every filename contains metadata splitted by "_", timestamp is after last "_" uint64_t timestamp = std::stoul(key.substr(key.find_last_of("_") + 1)); auto it = latest_parquets.find(path); From d1c85f68e8983a516690dffcfe5820d9dfa2b8a4 Mon Sep 17 00:00:00 2001 From: JackyWoo Date: Mon, 26 Sep 2022 18:29:15 +0800 Subject: [PATCH 027/266] manually snapshot creation for keeper --- docs/en/operations/clickhouse-keeper.md | 8 +++++++- src/Coordination/FourLetterCommand.cpp | 8 ++++++++ src/Coordination/FourLetterCommand.h | 13 +++++++++++++ src/Coordination/KeeperDispatcher.h | 6 ++++++ src/Coordination/KeeperServer.cpp | 5 +++++ src/Coordination/KeeperServer.h | 2 ++ .../test_keeper_four_word_command/test.py | 15 +++++++++++++++ 7 files changed, 56 insertions(+), 1 deletion(-) diff --git a/docs/en/operations/clickhouse-keeper.md b/docs/en/operations/clickhouse-keeper.md index 8bf64bca28f..6597e4e5be0 100644 --- a/docs/en/operations/clickhouse-keeper.md +++ b/docs/en/operations/clickhouse-keeper.md @@ -123,7 +123,7 @@ clickhouse keeper --config /etc/your_path_to_config/config.xml ClickHouse Keeper also provides 4lw commands which are almost the same with Zookeeper. Each command is composed of four letters such as `mntr`, `stat` etc. There are some more interesting commands: `stat` gives some general information about the server and connected clients, while `srvr` and `cons` give extended details on server and connections respectively. -The 4lw commands has a white list configuration `four_letter_word_white_list` which has default value `conf,cons,crst,envi,ruok,srst,srvr,stat,wchc,wchs,dirs,mntr,isro`. +The 4lw commands has a white list configuration `four_letter_word_white_list` which has default value `conf,cons,crst,envi,ruok,srst,srvr,stat,wchc,wchs,dirs,mntr,isro,csnp`. You can issue the commands to ClickHouse Keeper via telnet or nc, at the client port. @@ -306,6 +306,12 @@ Sessions with Ephemerals (1): /clickhouse/task_queue/ddl ``` +- `csnp`: Schedule a snapshot creation task. Return `"Snapshot creation scheduled."` if successfully scheduled or Fail to scheduled snapshot creation.` if failed. + +``` +Snapshot creation scheduled. +``` + ## [experimental] Migration from ZooKeeper {#migration-from-zookeeper} Seamlessly migration from ZooKeeper to ClickHouse Keeper is impossible you have to stop your ZooKeeper cluster, convert data and start ClickHouse Keeper. `clickhouse-keeper-converter` tool allows converting ZooKeeper logs and snapshots to ClickHouse Keeper snapshot. It works only with ZooKeeper > 3.4. Steps for migration: diff --git a/src/Coordination/FourLetterCommand.cpp b/src/Coordination/FourLetterCommand.cpp index c33630a913b..70009703c5a 100644 --- a/src/Coordination/FourLetterCommand.cpp +++ b/src/Coordination/FourLetterCommand.cpp @@ -136,6 +136,9 @@ void FourLetterCommandFactory::registerCommands(KeeperDispatcher & keeper_dispat FourLetterCommandPtr api_version_command = std::make_shared(keeper_dispatcher); factory.registerCommand(api_version_command); + FourLetterCommandPtr create_snapshot_command = std::make_shared(keeper_dispatcher); + factory.registerCommand(create_snapshot_command); + factory.initializeAllowList(keeper_dispatcher); factory.setInitialize(true); } @@ -472,4 +475,9 @@ String ApiVersionCommand::run() return toString(static_cast(Coordination::current_keeper_api_version)); } +String CreateSnapshotCommand::run() +{ + return keeper_dispatcher.createSnapshot() ? "Snapshot creation scheduled." : "Fail to scheduled snapshot creation."; +} + } diff --git a/src/Coordination/FourLetterCommand.h b/src/Coordination/FourLetterCommand.h index 8a98b94b33a..25cc281d5e1 100644 --- a/src/Coordination/FourLetterCommand.h +++ b/src/Coordination/FourLetterCommand.h @@ -327,4 +327,17 @@ struct ApiVersionCommand : public IFourLetterCommand String run() override; ~ApiVersionCommand() override = default; }; + +/// Create snapshot manually +struct CreateSnapshotCommand : public IFourLetterCommand +{ + explicit CreateSnapshotCommand(KeeperDispatcher & keeper_dispatcher_) + : IFourLetterCommand(keeper_dispatcher_) + { + } + + String name() override { return "csnp"; } + String run() override; + ~CreateSnapshotCommand() override = default; +}; } diff --git a/src/Coordination/KeeperDispatcher.h b/src/Coordination/KeeperDispatcher.h index 5e2701299f4..9b52721b951 100644 --- a/src/Coordination/KeeperDispatcher.h +++ b/src/Coordination/KeeperDispatcher.h @@ -201,6 +201,12 @@ public: { keeper_stats.reset(); } + + /// Create snapshot manually + bool createSnapshot() + { + return server->createSnapshot(); + } }; } diff --git a/src/Coordination/KeeperServer.cpp b/src/Coordination/KeeperServer.cpp index 8186ddd0c00..f03f453aada 100644 --- a/src/Coordination/KeeperServer.cpp +++ b/src/Coordination/KeeperServer.cpp @@ -903,4 +903,9 @@ Keeper4LWInfo KeeperServer::getPartiallyFilled4LWInfo() const return result; } +bool KeeperServer::createSnapshot() +{ + return raft_instance->create_snapshot(); +} + } diff --git a/src/Coordination/KeeperServer.h b/src/Coordination/KeeperServer.h index 6873ef2a01e..f969e9ee063 100644 --- a/src/Coordination/KeeperServer.h +++ b/src/Coordination/KeeperServer.h @@ -130,6 +130,8 @@ public: /// Wait configuration update for action. Used by followers. /// Return true if update was successfully received. bool waitConfigurationUpdate(const ConfigUpdateAction & task); + + bool createSnapshot(); }; } diff --git a/tests/integration/test_keeper_four_word_command/test.py b/tests/integration/test_keeper_four_word_command/test.py index e8136d322d3..0995adb199c 100644 --- a/tests/integration/test_keeper_four_word_command/test.py +++ b/tests/integration/test_keeper_four_word_command/test.py @@ -634,3 +634,18 @@ def test_cmd_wchp(started_cluster): assert "/test_4lw_normal_node_1" in list_data finally: destroy_zk_client(zk) + + +def test_cmd_csnp(started_cluster): + zk = None + try: + wait_nodes() + clear_znodes() + reset_node_stats() + + zk = get_fake_zk(node1.name, timeout=30.0) + + data = send_4lw_cmd(cmd="csnp") + assert data == "Snapshot creation scheduled." + finally: + destroy_zk_client(zk) From dfb2be3a6732aed0890a82b9d79d12dc1b8ea841 Mon Sep 17 00:00:00 2001 From: JackyWoo Date: Mon, 26 Sep 2022 18:34:53 +0800 Subject: [PATCH 028/266] fix docs --- docs/en/operations/clickhouse-keeper.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/en/operations/clickhouse-keeper.md b/docs/en/operations/clickhouse-keeper.md index 6597e4e5be0..1b8b1e02aa8 100644 --- a/docs/en/operations/clickhouse-keeper.md +++ b/docs/en/operations/clickhouse-keeper.md @@ -123,7 +123,7 @@ clickhouse keeper --config /etc/your_path_to_config/config.xml ClickHouse Keeper also provides 4lw commands which are almost the same with Zookeeper. Each command is composed of four letters such as `mntr`, `stat` etc. There are some more interesting commands: `stat` gives some general information about the server and connected clients, while `srvr` and `cons` give extended details on server and connections respectively. -The 4lw commands has a white list configuration `four_letter_word_white_list` which has default value `conf,cons,crst,envi,ruok,srst,srvr,stat,wchc,wchs,dirs,mntr,isro,csnp`. +The 4lw commands has a white list configuration `four_letter_word_white_list` which has default value `conf,cons,crst,envi,ruok,srst,srvr,stat,wchc,wchs,dirs,mntr,isro`. You can issue the commands to ClickHouse Keeper via telnet or nc, at the client port. From 13e50c771c3b18db30e817b9ba7ae8a6fe1abb6e Mon Sep 17 00:00:00 2001 From: Daniil Rubin Date: Wed, 28 Sep 2022 11:03:43 +0000 Subject: [PATCH 029/266] Fix bug, use static method --- src/Storages/StorageDelta.cpp | 52 ++------------------------------ src/Storages/StorageDelta.h | 2 -- src/Storages/StorageHudi.cpp | 56 +++-------------------------------- src/Storages/StorageHudi.h | 4 --- src/Storages/StorageS3.h | 2 ++ 5 files changed, 8 insertions(+), 108 deletions(-) diff --git a/src/Storages/StorageDelta.cpp b/src/Storages/StorageDelta.cpp index 951337f88c3..2c7f348ef94 100644 --- a/src/Storages/StorageDelta.cpp +++ b/src/Storages/StorageDelta.cpp @@ -181,7 +181,7 @@ StorageDelta::StorageDelta( , table_path(uri_.key) { StorageInMemoryMetadata storage_metadata; - updateS3Configuration(context_, base_configuration); + StorageS3::updateS3Configuration(context_, base_configuration); JsonMetadataGetter getter{base_configuration, table_path}; @@ -229,65 +229,17 @@ Pipe StorageDelta::read( size_t max_block_size, unsigned num_streams) { - updateS3Configuration(context, base_configuration); + StorageS3::updateS3Configuration(context, base_configuration); return s3engine->read(column_names, storage_snapshot, query_info, context, processed_stage, max_block_size, num_streams); } -void StorageDelta::updateS3Configuration(ContextPtr ctx, StorageS3::S3Configuration & upd) -{ - auto settings = ctx->getStorageS3Settings().getSettings(upd.uri.uri.toString()); - - bool need_update_configuration = settings != S3Settings{}; - if (need_update_configuration) - { - if (upd.rw_settings != settings.rw_settings) - upd.rw_settings = settings.rw_settings; - } - - upd.rw_settings.updateFromSettingsIfEmpty(ctx->getSettings()); - - if (upd.client && (!upd.access_key_id.empty() || settings.auth_settings == upd.auth_settings)) - return; - - Aws::Auth::AWSCredentials credentials(upd.access_key_id, upd.secret_access_key); - HeaderCollection headers; - if (upd.access_key_id.empty()) - { - credentials = Aws::Auth::AWSCredentials(settings.auth_settings.access_key_id, settings.auth_settings.secret_access_key); - headers = settings.auth_settings.headers; - } - - S3::PocoHTTPClientConfiguration client_configuration = S3::ClientFactory::instance().createClientConfiguration( - settings.auth_settings.region, - ctx->getRemoteHostFilter(), - ctx->getGlobalContext()->getSettingsRef().s3_max_redirects, - ctx->getGlobalContext()->getSettingsRef().enable_s3_requests_logging, - /* for_disk_s3 = */ false); - - client_configuration.endpointOverride = upd.uri.endpoint; - client_configuration.maxConnections = upd.rw_settings.max_connections; - - upd.client = S3::ClientFactory::instance().create( - client_configuration, - upd.uri.is_virtual_hosted_style, - credentials.GetAWSAccessKeyId(), - credentials.GetAWSSecretKey(), - settings.auth_settings.server_side_encryption_customer_key_base64, - std::move(headers), - settings.auth_settings.use_environment_credentials.value_or(ctx->getConfigRef().getBool("s3.use_environment_credentials", false)), - settings.auth_settings.use_insecure_imds_request.value_or(ctx->getConfigRef().getBool("s3.use_insecure_imds_request", false))); - - upd.auth_settings = std::move(settings.auth_settings); -} - String StorageDelta::generateQueryFromKeys(std::vector && keys) { std::string new_query = fmt::format("{{{}}}", fmt::join(keys, ",")); return new_query; } - void registerStorageDelta(StorageFactory & factory) { factory.registerStorage( diff --git a/src/Storages/StorageDelta.h b/src/Storages/StorageDelta.h index fb6e19b7488..7b25d2c618e 100644 --- a/src/Storages/StorageDelta.h +++ b/src/Storages/StorageDelta.h @@ -87,8 +87,6 @@ public: private: void Init(); - static void updateS3Configuration(ContextPtr, StorageS3::S3Configuration &); - static String generateQueryFromKeys(std::vector && keys); StorageS3::S3Configuration base_configuration; diff --git a/src/Storages/StorageHudi.cpp b/src/Storages/StorageHudi.cpp index 55f7e4b9c10..80555efcafd 100644 --- a/src/Storages/StorageHudi.cpp +++ b/src/Storages/StorageHudi.cpp @@ -39,7 +39,7 @@ StorageHudi::StorageHudi( , table_path(uri_.key) { StorageInMemoryMetadata storage_metadata; - updateS3Configuration(context_, base_configuration); + StorageS3::updateS3Configuration(context_, base_configuration); auto keys = getKeysFromS3(); @@ -84,59 +84,11 @@ Pipe StorageHudi::read( QueryProcessingStage::Enum processed_stage, size_t max_block_size, unsigned num_streams) -{ - updateS3Configuration(context, base_configuration); - +{ + StorageS3::updateS3Configuration(context, base_configuration); return s3engine->read(column_names, storage_snapshot, query_info, context, processed_stage, max_block_size, num_streams); } -void StorageHudi::updateS3Configuration(ContextPtr ctx, StorageS3::S3Configuration & upd) -{ - auto settings = ctx->getStorageS3Settings().getSettings(upd.uri.uri.toString()); - - bool need_update_configuration = settings != S3Settings{}; - if (need_update_configuration) - { - if (upd.rw_settings != settings.rw_settings) - upd.rw_settings = settings.rw_settings; - } - - upd.rw_settings.updateFromSettingsIfEmpty(ctx->getSettings()); - - if (upd.client && (!upd.access_key_id.empty() || settings.auth_settings == upd.auth_settings)) - return; - - Aws::Auth::AWSCredentials credentials(upd.access_key_id, upd.secret_access_key); - HeaderCollection headers; - if (upd.access_key_id.empty()) - { - credentials = Aws::Auth::AWSCredentials(settings.auth_settings.access_key_id, settings.auth_settings.secret_access_key); - headers = settings.auth_settings.headers; - } - - S3::PocoHTTPClientConfiguration client_configuration = S3::ClientFactory::instance().createClientConfiguration( - settings.auth_settings.region, - ctx->getRemoteHostFilter(), - ctx->getGlobalContext()->getSettingsRef().s3_max_redirects, - ctx->getGlobalContext()->getSettingsRef().enable_s3_requests_logging, - /* for_disk_s3 = */ false); - - client_configuration.endpointOverride = upd.uri.endpoint; - client_configuration.maxConnections = upd.rw_settings.max_connections; - - upd.client = S3::ClientFactory::instance().create( - client_configuration, - upd.uri.is_virtual_hosted_style, - credentials.GetAWSAccessKeyId(), - credentials.GetAWSSecretKey(), - settings.auth_settings.server_side_encryption_customer_key_base64, - std::move(headers), - settings.auth_settings.use_environment_credentials.value_or(ctx->getConfigRef().getBool("s3.use_environment_credentials", false)), - settings.auth_settings.use_insecure_imds_request.value_or(ctx->getConfigRef().getBool("s3.use_insecure_imds_request", false))); - - upd.auth_settings = std::move(settings.auth_settings); -} - std::vector StorageHudi::getKeysFromS3() { std::vector keys; @@ -186,7 +138,7 @@ std::string StorageHudi::generateQueryFromKeys(std::vector && keys) keys, [](const std::string & s) { - return std::filesystem::path(s).extension() != "parquet"; + return std::filesystem::path(s).extension() != ".parquet"; }); // for each partition path take only latest parquet file diff --git a/src/Storages/StorageHudi.h b/src/Storages/StorageHudi.h index ceb12f5f550..e0d5cf5a329 100644 --- a/src/Storages/StorageHudi.h +++ b/src/Storages/StorageHudi.h @@ -44,14 +44,10 @@ public: size_t max_block_size, unsigned num_streams) override; -private: - static void updateS3Configuration(ContextPtr, StorageS3::S3Configuration &); - private: std::vector getKeysFromS3(); static std::string generateQueryFromKeys(std::vector && keys); -private: StorageS3::S3Configuration base_configuration; std::shared_ptr s3engine; Poco::Logger * log; diff --git a/src/Storages/StorageS3.h b/src/Storages/StorageS3.h index 3a02237570d..b6589bf49aa 100644 --- a/src/Storages/StorageS3.h +++ b/src/Storages/StorageS3.h @@ -216,6 +216,8 @@ public: private: friend class StorageS3Cluster; friend class TableFunctionS3Cluster; + friend class StorageHudi; + friend class StorageDelta; S3Configuration s3_configuration; std::vector keys; From db7cb4e9b5249c7d31ffbe377d3feffcbc2b0af4 Mon Sep 17 00:00:00 2001 From: Daniil Rubin Date: Wed, 28 Sep 2022 11:21:32 +0000 Subject: [PATCH 030/266] apply clang-format --- src/Storages/StorageDelta.cpp | 54 +++++++++++++++++++---------------- src/Storages/StorageDelta.h | 10 +++---- src/Storages/StorageHudi.cpp | 27 +++++++----------- src/Storages/StorageHudi.h | 4 +-- 4 files changed, 47 insertions(+), 48 deletions(-) diff --git a/src/Storages/StorageDelta.cpp b/src/Storages/StorageDelta.cpp index 2c7f348ef94..027312385bb 100644 --- a/src/Storages/StorageDelta.cpp +++ b/src/Storages/StorageDelta.cpp @@ -2,25 +2,25 @@ #if USE_AWS_S3 -#include -#include +# include +# include -#include -#include -#include -#include +# include +# include +# include +# include -#include -#include +# include +# include -#include -#include -#include +# include +# include +# include -#include +# include -#include -#include +# include +# include namespace DB { @@ -73,16 +73,17 @@ void JsonMetadataGetter::Init() auto buf = createS3ReadBuffer(key); while (!buf->eof()) - { + { // may be some invalid characters before json char c; - while ( buf->peek(c) && c != '{') buf->ignore(); - if (buf->eof()) + while (buf->peek(c) && c != '{') + buf->ignore(); + if (buf->eof()) break; String json_str; readJSONObjectPossiblyInvalid(json_str, *buf); - + if (json_str.empty()) continue; @@ -148,7 +149,8 @@ std::shared_ptr JsonMetadataGetter::createS3ReadBuffer(const String ReadSettings{}); } -void JsonMetadataGetter::handleJSON(const JSON & json) { +void JsonMetadataGetter::handleJSON(const JSON & json) +{ if (json.has("add")) { auto path = json["add"]["path"].getString(); @@ -195,8 +197,7 @@ StorageDelta::StorageDelta( if (columns_.empty()) { - columns_ - = StorageS3::getTableStructureFromData(format_name_, s3_uri, access_key_, secret_access_key_, "", false, {}, context_); + columns_ = StorageS3::getTableStructureFromData(format_name_, s3_uri, access_key_, secret_access_key_, "", false, {}, context_); storage_metadata.setColumns(columns_); } else @@ -248,15 +249,18 @@ void registerStorageDelta(StorageFactory & factory) { auto & engine_args = args.engine_args; if (engine_args.empty() || engine_args.size() < 3) - throw Exception(ErrorCodes::NUMBER_OF_ARGUMENTS_DOESNT_MATCH, "Storage DeltaLake requires 3 to 4 arguments: table_url, access_key, secret_access_key, [format]"); + throw Exception( + ErrorCodes::NUMBER_OF_ARGUMENTS_DOESNT_MATCH, + "Storage DeltaLake requires 3 to 4 arguments: table_url, access_key, secret_access_key, [format]"); + - String table_url = checkAndGetLiteralArgument(engine_args[0], "url"); String access_key_id = checkAndGetLiteralArgument(engine_args[1], "access_key_id"); String secret_access_key = checkAndGetLiteralArgument(engine_args[2], "secret_access_key"); - + String format = "Parquet"; - if (engine_args.size() == 4) { + if (engine_args.size() == 4) + { format = checkAndGetLiteralArgument(engine_args[3], "format"); } diff --git a/src/Storages/StorageDelta.h b/src/Storages/StorageDelta.h index 7b25d2c618e..c5dc0b2fd07 100644 --- a/src/Storages/StorageDelta.h +++ b/src/Storages/StorageDelta.h @@ -4,11 +4,11 @@ #if USE_AWS_S3 -#include -#include +# include +# include -#include -#include +# include +# include namespace Poco { @@ -52,7 +52,7 @@ private: std::vector getJsonLogFiles(); std::shared_ptr createS3ReadBuffer(const String & key); - + void handleJSON(const JSON & json); StorageS3::S3Configuration base_configuration; diff --git a/src/Storages/StorageHudi.cpp b/src/Storages/StorageHudi.cpp index 80555efcafd..2da5a1e8b5e 100644 --- a/src/Storages/StorageHudi.cpp +++ b/src/Storages/StorageHudi.cpp @@ -2,18 +2,18 @@ #if USE_AWS_S3 -#include -#include +# include +# include -#include -#include -#include +# include +# include +# include -#include -#include -#include +# include +# include +# include -#include +# include namespace DB { @@ -84,7 +84,7 @@ Pipe StorageHudi::read( QueryProcessingStage::Enum processed_stage, size_t max_block_size, unsigned num_streams) -{ +{ StorageS3::updateS3Configuration(context, base_configuration); return s3engine->read(column_names, storage_snapshot, query_info, context, processed_stage, max_block_size, num_streams); } @@ -134,12 +134,7 @@ std::vector StorageHudi::getKeysFromS3() std::string StorageHudi::generateQueryFromKeys(std::vector && keys) { // filter only .parquet files - std::erase_if( - keys, - [](const std::string & s) - { - return std::filesystem::path(s).extension() != ".parquet"; - }); + std::erase_if(keys, [](const std::string & s) { return std::filesystem::path(s).extension() != ".parquet"; }); // for each partition path take only latest parquet file diff --git a/src/Storages/StorageHudi.h b/src/Storages/StorageHudi.h index e0d5cf5a329..d782106d2b0 100644 --- a/src/Storages/StorageHudi.h +++ b/src/Storages/StorageHudi.h @@ -4,8 +4,8 @@ #if USE_AWS_S3 -#include -#include +# include +# include namespace Poco { From 0d3670d516f8dbae493784dc50c09c726d21793a Mon Sep 17 00:00:00 2001 From: Daniil Rubin Date: Wed, 28 Sep 2022 17:30:15 +0000 Subject: [PATCH 031/266] Fix typo --- src/Storages/StorageHudi.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Storages/StorageHudi.cpp b/src/Storages/StorageHudi.cpp index 2da5a1e8b5e..4db372e7e96 100644 --- a/src/Storages/StorageHudi.cpp +++ b/src/Storages/StorageHudi.cpp @@ -153,7 +153,7 @@ std::string StorageHudi::generateQueryFromKeys(std::vector && keys) path = key.substr(0, slash); } - // every filename contains metadata splitted by "_", timestamp is after last "_" + // every filename contains metadata split by "_", timestamp is after last "_" uint64_t timestamp = std::stoul(key.substr(key.find_last_of("_") + 1)); auto it = latest_parquets.find(path); From 74f026ba65f53c54f33bb83d175a40d17465e66e Mon Sep 17 00:00:00 2001 From: Daniil Rubin Date: Thu, 29 Sep 2022 20:15:27 +0000 Subject: [PATCH 032/266] Change logic error, add format argument to Hudi engine --- src/Storages/StorageHudi.cpp | 49 ++++++++++++++++++++++-------------- src/Storages/StorageHudi.h | 3 ++- 2 files changed, 32 insertions(+), 20 deletions(-) diff --git a/src/Storages/StorageHudi.cpp b/src/Storages/StorageHudi.cpp index 4db372e7e96..78f3a0dd319 100644 --- a/src/Storages/StorageHudi.cpp +++ b/src/Storages/StorageHudi.cpp @@ -20,6 +20,7 @@ namespace DB namespace ErrorCodes { + extern const int NUMBER_OF_ARGUMENTS_DOESNT_MATCH; extern const int BAD_ARGUMENTS; extern const int S3_ERROR; } @@ -29,6 +30,7 @@ StorageHudi::StorageHudi( const String & access_key_, const String & secret_access_key_, const StorageID & table_id_, + const String & format_, ColumnsDescription columns_, const ConstraintsDescription & constraints_, const String & comment, @@ -43,7 +45,7 @@ StorageHudi::StorageHudi( auto keys = getKeysFromS3(); - auto new_uri = base_configuration.uri.uri.toString() + generateQueryFromKeys(std::move(keys)); + auto new_uri = base_configuration.uri.uri.toString() + generateQueryFromKeys(std::move(keys), format_); LOG_DEBUG(log, "New uri: {}", new_uri); LOG_DEBUG(log, "Table path: {}", table_path); @@ -51,8 +53,7 @@ StorageHudi::StorageHudi( if (columns_.empty()) { - columns_ - = StorageS3::getTableStructureFromData(String("Parquet"), s3_uri, access_key_, secret_access_key_, "", false, {}, context_); + columns_ = StorageS3::getTableStructureFromData(format_, s3_uri, access_key_, secret_access_key_, "", false, {}, context_); storage_metadata.setColumns(columns_); } else @@ -67,7 +68,7 @@ StorageHudi::StorageHudi( access_key_, secret_access_key_, table_id_, - String("Parquet"), // format name + format_, base_configuration.rw_settings, columns_, constraints_, @@ -131,12 +132,15 @@ std::vector StorageHudi::getKeysFromS3() return keys; } -std::string StorageHudi::generateQueryFromKeys(std::vector && keys) +std::string StorageHudi::generateQueryFromKeys(std::vector && keys, String format) { - // filter only .parquet files - std::erase_if(keys, [](const std::string & s) { return std::filesystem::path(s).extension() != ".parquet"; }); + // make format lowercase + std::transform(format.begin(), format.end(), format.begin(), [](unsigned char c) { return std::tolower(c); }); - // for each partition path take only latest parquet file + // filter only files with specific format + std::erase_if(keys, [&format](const std::string & s) { return std::filesystem::path(s).extension() != "." + format; }); + + // for each partition path take only latest file std::unordered_map> latest_parquets; @@ -198,23 +202,30 @@ void registerStorageHudi(StorageFactory & factory) [](const StorageFactory::Arguments & args) { auto & engine_args = args.engine_args; - if (engine_args.empty()) - throw Exception(ErrorCodes::BAD_ARGUMENTS, "External data source must have arguments"); - - auto configuration = StorageS3::getConfiguration(engine_args, args.getLocalContext()); - - configuration.url = checkAndGetLiteralArgument(engine_args[0], "url"); - configuration.auth_settings.access_key_id = checkAndGetLiteralArgument(engine_args[1], "access_key_id"); - configuration.auth_settings.secret_access_key = checkAndGetLiteralArgument(engine_args[2], "secret_access_key"); + if (engine_args.empty() || engine_args.size() < 3) + throw Exception( + ErrorCodes::NUMBER_OF_ARGUMENTS_DOESNT_MATCH, + "Storage Hudi requires 3 to 4 arguments: table_url, access_key, secret_access_key, [format]"); - S3::URI s3_uri(Poco::URI(configuration.url)); + String table_url = checkAndGetLiteralArgument(engine_args[0], "url"); + String access_key_id = checkAndGetLiteralArgument(engine_args[1], "access_key_id"); + String secret_access_key = checkAndGetLiteralArgument(engine_args[2], "secret_access_key"); + + String format = "Parquet"; + if (engine_args.size() == 4) + { + format = checkAndGetLiteralArgument(engine_args[3], "format"); + } + + auto s3_uri = S3::URI(Poco::URI(table_url)); return std::make_shared( s3_uri, - configuration.auth_settings.access_key_id, - configuration.auth_settings.secret_access_key, + access_key_id, + secret_access_key, args.table_id, + format, args.columns, args.constraints, args.comment, diff --git a/src/Storages/StorageHudi.h b/src/Storages/StorageHudi.h index d782106d2b0..7b647345441 100644 --- a/src/Storages/StorageHudi.h +++ b/src/Storages/StorageHudi.h @@ -28,6 +28,7 @@ public: const String & access_key_, const String & secret_access_key_, const StorageID & table_id_, + const String & format_, ColumnsDescription columns_, const ConstraintsDescription & constraints_, const String & comment, @@ -46,7 +47,7 @@ public: private: std::vector getKeysFromS3(); - static std::string generateQueryFromKeys(std::vector && keys); + static std::string generateQueryFromKeys(std::vector && keys, String format); StorageS3::S3Configuration base_configuration; std::shared_ptr s3engine; From 97dd75194a92913ef91bd2bc23d8203694ff171e Mon Sep 17 00:00:00 2001 From: Daniil Rubin Date: Thu, 29 Sep 2022 20:36:54 +0000 Subject: [PATCH 033/266] Remove unused error code --- src/Storages/StorageHudi.cpp | 1 - 1 file changed, 1 deletion(-) diff --git a/src/Storages/StorageHudi.cpp b/src/Storages/StorageHudi.cpp index 78f3a0dd319..4aff4ff3a43 100644 --- a/src/Storages/StorageHudi.cpp +++ b/src/Storages/StorageHudi.cpp @@ -21,7 +21,6 @@ namespace DB namespace ErrorCodes { extern const int NUMBER_OF_ARGUMENTS_DOESNT_MATCH; - extern const int BAD_ARGUMENTS; extern const int S3_ERROR; } From c813bbbddc5caffc486d014e333156dbc34a767a Mon Sep 17 00:00:00 2001 From: Alexey Milovidov Date: Sat, 1 Oct 2022 23:00:23 +0200 Subject: [PATCH 034/266] Add a test --- .../0_stateless/02456_progress_tty.expect | 45 +++++++++++++++++++ .../0_stateless/02456_progress_tty.reference | 0 2 files changed, 45 insertions(+) create mode 100755 tests/queries/0_stateless/02456_progress_tty.expect create mode 100644 tests/queries/0_stateless/02456_progress_tty.reference diff --git a/tests/queries/0_stateless/02456_progress_tty.expect b/tests/queries/0_stateless/02456_progress_tty.expect new file mode 100755 index 00000000000..32ed8fd9094 --- /dev/null +++ b/tests/queries/0_stateless/02456_progress_tty.expect @@ -0,0 +1,45 @@ +#!/usr/bin/expect -f + +set basedir [file dirname $argv0] +set basename [file tail $argv0] +#exp_internal -f $env(CLICKHOUSE_TMP)/$basename.debuglog 0 + +log_user 0 +set timeout 60 +match_max 100000 +set stty_init "rows 25 cols 80" + +expect_after { + # Do not ignore eof from expect + eof { exp_continue } + # A default timeout action is to do nothing, change it to fail + timeout { exit 1 } +} + +# Progress is displayed by default +spawn bash -c "source $basedir/../shell_config.sh ; \$CLICKHOUSE_LOCAL --query 'SELECT sum(sleep(1) = 0) FROM numbers(3) SETTINGS max_block_size = 1' >/dev/null" +expect "Progress: " +expect "█" + +# It is true even if we redirect both stdout and stderr to /dev/null +spawn bash -c "source $basedir/../shell_config.sh ; \$CLICKHOUSE_LOCAL --query 'SELECT sum(sleep(1) = 0) FROM numbers(3) SETTINGS max_block_size = 1' >/dev/null 2>&1" +expect "Progress: " +expect "█" + +# The option --progress has implicit value of true +spawn bash -c "source $basedir/../shell_config.sh ; \$CLICKHOUSE_LOCAL --progress --query 'SELECT sum(sleep(1) = 0) FROM numbers(3) SETTINGS max_block_size = 1' >/dev/null 2>&1" +expect "Progress: " +expect "█" + +# But we can set it to false +spawn bash -c "source $basedir/../shell_config.sh ; \$CLICKHOUSE_LOCAL --progress false --query 'SELECT sum(sleep(1) = 0) FROM numbers(3) SETTINGS max_block_size = 1' 2>/dev/null" +expect -exact "3\n" + +# As well as to 0 for the same effect +spawn bash -c "source $basedir/../shell_config.sh ; \$CLICKHOUSE_LOCAL --progress 0 --query 'SELECT sum(sleep(1) = 0) FROM numbers(3) SETTINGS max_block_size = 1' 2>/dev/null" +expect -exact "3\n" + +# If we set it to 1, the progress will be displayed as well +spawn bash -c "source $basedir/../shell_config.sh ; \$CLICKHOUSE_LOCAL --progress 1 --query 'SELECT sum(sleep(1) = 0) FROM numbers(3) SETTINGS max_block_size = 1' >/dev/null 2>&1" +expect "Progress: " +expect "█" diff --git a/tests/queries/0_stateless/02456_progress_tty.reference b/tests/queries/0_stateless/02456_progress_tty.reference new file mode 100644 index 00000000000..e69de29bb2d From 39633120ab969ce2e710dd8d530e472325e0e5a1 Mon Sep 17 00:00:00 2001 From: Alexey Milovidov Date: Sat, 1 Oct 2022 23:17:49 +0200 Subject: [PATCH 035/266] Add a test --- .../0_stateless/02456_progress_tty.expect | 34 ++++++++++++------- 1 file changed, 22 insertions(+), 12 deletions(-) diff --git a/tests/queries/0_stateless/02456_progress_tty.expect b/tests/queries/0_stateless/02456_progress_tty.expect index 32ed8fd9094..6a96c88c05c 100755 --- a/tests/queries/0_stateless/02456_progress_tty.expect +++ b/tests/queries/0_stateless/02456_progress_tty.expect @@ -4,42 +4,52 @@ set basedir [file dirname $argv0] set basename [file tail $argv0] #exp_internal -f $env(CLICKHOUSE_TMP)/$basename.debuglog 0 -log_user 0 +log_user 1 set timeout 60 match_max 100000 -set stty_init "rows 25 cols 80" +set stty_init "rows 25 cols 120" expect_after { - # Do not ignore eof from expect eof { exp_continue } - # A default timeout action is to do nothing, change it to fail timeout { exit 1 } } +spawn bash +send "source $basedir/../shell_config.sh\r" + # Progress is displayed by default -spawn bash -c "source $basedir/../shell_config.sh ; \$CLICKHOUSE_LOCAL --query 'SELECT sum(sleep(1) = 0) FROM numbers(3) SETTINGS max_block_size = 1' >/dev/null" +send "\$CLICKHOUSE_LOCAL --query 'SELECT sum(sleep(1) = 0) FROM numbers(3) SETTINGS max_block_size = 1' >/dev/null\r" expect "Progress: " expect "█" +send "\3" # It is true even if we redirect both stdout and stderr to /dev/null -spawn bash -c "source $basedir/../shell_config.sh ; \$CLICKHOUSE_LOCAL --query 'SELECT sum(sleep(1) = 0) FROM numbers(3) SETTINGS max_block_size = 1' >/dev/null 2>&1" +send "\$CLICKHOUSE_LOCAL --query 'SELECT sum(sleep(1) = 0) FROM numbers(3) SETTINGS max_block_size = 1' >/dev/null 2>&1\r" expect "Progress: " expect "█" +send "\3" # The option --progress has implicit value of true -spawn bash -c "source $basedir/../shell_config.sh ; \$CLICKHOUSE_LOCAL --progress --query 'SELECT sum(sleep(1) = 0) FROM numbers(3) SETTINGS max_block_size = 1' >/dev/null 2>&1" +send "\$CLICKHOUSE_LOCAL --progress --query 'SELECT sum(sleep(1) = 0) FROM numbers(3) SETTINGS max_block_size = 1' >/dev/null 2>&1\r" expect "Progress: " expect "█" +send "\3" # But we can set it to false -spawn bash -c "source $basedir/../shell_config.sh ; \$CLICKHOUSE_LOCAL --progress false --query 'SELECT sum(sleep(1) = 0) FROM numbers(3) SETTINGS max_block_size = 1' 2>/dev/null" -expect -exact "3\n" +send "\$CLICKHOUSE_LOCAL --progress false --query 'SELECT sleep(1), \$\$Hello\$\$ FROM numbers(3) SETTINGS max_block_size = 1' 2>/dev/null\r" +expect -exact "0\tHello\r\n" +send "\3" # As well as to 0 for the same effect -spawn bash -c "source $basedir/../shell_config.sh ; \$CLICKHOUSE_LOCAL --progress 0 --query 'SELECT sum(sleep(1) = 0) FROM numbers(3) SETTINGS max_block_size = 1' 2>/dev/null" -expect -exact "3\n" +send "\$CLICKHOUSE_LOCAL --progress 0 --query 'SELECT sleep(1), \$\$Hello\$\$ FROM numbers(3) SETTINGS max_block_size = 1' 2>/dev/null\r" +expect -exact "0\tHello\r\n" +send "\3" # If we set it to 1, the progress will be displayed as well -spawn bash -c "source $basedir/../shell_config.sh ; \$CLICKHOUSE_LOCAL --progress 1 --query 'SELECT sum(sleep(1) = 0) FROM numbers(3) SETTINGS max_block_size = 1' >/dev/null 2>&1" +send "\$CLICKHOUSE_LOCAL --progress 1 --query 'SELECT sum(sleep(1) = 0) FROM numbers(3) SETTINGS max_block_size = 1' >/dev/null 2>&1\r" expect "Progress: " expect "█" +send "\3" + +send "exit\r" +expect eof From 9a54af7df936a6f50c1458f878a228c050625ded Mon Sep 17 00:00:00 2001 From: Alexey Milovidov Date: Sat, 1 Oct 2022 23:18:54 +0200 Subject: [PATCH 036/266] Add a test --- tests/queries/0_stateless/02456_progress_tty.expect | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/queries/0_stateless/02456_progress_tty.expect b/tests/queries/0_stateless/02456_progress_tty.expect index 6a96c88c05c..90be3b1076e 100755 --- a/tests/queries/0_stateless/02456_progress_tty.expect +++ b/tests/queries/0_stateless/02456_progress_tty.expect @@ -4,7 +4,7 @@ set basedir [file dirname $argv0] set basename [file tail $argv0] #exp_internal -f $env(CLICKHOUSE_TMP)/$basename.debuglog 0 -log_user 1 +log_user 0 set timeout 60 match_max 100000 set stty_init "rows 25 cols 120" From b65f674dd488cd4da91e0a50b20744c0b74cbbdf Mon Sep 17 00:00:00 2001 From: Alexey Milovidov Date: Sat, 1 Oct 2022 23:19:02 +0200 Subject: [PATCH 037/266] Add a test --- tests/queries/0_stateless/02456_progress_tty.expect | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/queries/0_stateless/02456_progress_tty.expect b/tests/queries/0_stateless/02456_progress_tty.expect index 90be3b1076e..9daa6caa3fa 100755 --- a/tests/queries/0_stateless/02456_progress_tty.expect +++ b/tests/queries/0_stateless/02456_progress_tty.expect @@ -2,7 +2,7 @@ set basedir [file dirname $argv0] set basename [file tail $argv0] -#exp_internal -f $env(CLICKHOUSE_TMP)/$basename.debuglog 0 +exp_internal -f $env(CLICKHOUSE_TMP)/$basename.debuglog 0 log_user 0 set timeout 60 From 5d710e21f108b736028afd3341d9d8029bb7c024 Mon Sep 17 00:00:00 2001 From: Alexey Milovidov Date: Sat, 1 Oct 2022 23:19:36 +0200 Subject: [PATCH 038/266] Render progress directly in tty --- programs/client/Client.cpp | 2 +- programs/local/LocalServer.cpp | 2 +- src/Client/ClientBase.cpp | 66 ++++++++++++++++++++++--------- src/Client/ClientBase.h | 6 ++- src/Common/ProgressIndication.cpp | 32 +++++++-------- src/Common/ProgressIndication.h | 14 ++++--- 6 files changed, 76 insertions(+), 46 deletions(-) diff --git a/programs/client/Client.cpp b/programs/client/Client.cpp index 303c8c2ce4f..cc3bbbb63ca 100644 --- a/programs/client/Client.cpp +++ b/programs/client/Client.cpp @@ -1025,7 +1025,7 @@ void Client::processConfig() } else { - need_render_progress = config().getBool("progress", false); + need_render_progress = config().getBool("progress", true); echo_queries = config().getBool("echo", false); ignore_error = config().getBool("ignore-error", false); diff --git a/programs/local/LocalServer.cpp b/programs/local/LocalServer.cpp index 2b9d819f5eb..26d5acafbec 100644 --- a/programs/local/LocalServer.cpp +++ b/programs/local/LocalServer.cpp @@ -488,7 +488,7 @@ void LocalServer::processConfig() } else { - need_render_progress = config().getBool("progress", false); + need_render_progress = config().getBool("progress", true); echo_queries = config().hasOption("echo") || config().hasOption("verbose"); ignore_error = config().getBool("ignore-error", false); is_multiquery = true; diff --git a/src/Client/ClientBase.cpp b/src/Client/ClientBase.cpp index f407fab68f1..566ee49fba3 100644 --- a/src/Client/ClientBase.cpp +++ b/src/Client/ClientBase.cpp @@ -415,7 +415,7 @@ void ClientBase::onData(Block & block, ASTPtr parsed_query) /// If results are written INTO OUTFILE, we can avoid clearing progress to avoid flicker. if (need_render_progress && (stdout_is_a_tty || is_interactive) && (!select_into_file || select_into_file_and_stdout)) - progress_indication.clearProgressOutput(); + progress_indication.clearProgressOutput(*tty_buf); try { @@ -436,7 +436,7 @@ void ClientBase::onData(Block & block, ASTPtr parsed_query) { if (select_into_file && !select_into_file_and_stdout) std::cerr << "\r"; - progress_indication.writeProgress(); + progress_indication.writeProgress(*tty_buf); } } @@ -444,7 +444,8 @@ void ClientBase::onData(Block & block, ASTPtr parsed_query) void ClientBase::onLogData(Block & block) { initLogsOutputStream(); - progress_indication.clearProgressOutput(); + if (need_render_progress) + progress_indication.clearProgressOutput(*tty_buf); logs_out_stream->writeLogs(block); logs_out_stream->flush(); } @@ -640,6 +641,33 @@ void ClientBase::initLogsOutputStream() } } +void ClientBase::initTtyBuffer() +{ + if (!tty_buf) + { + static constexpr auto tty_file_name = "/dev/tty"; + + /// Output all progress bar commands to stderr at once to avoid flicker. + /// This size is usually greater than the window size. + static constexpr size_t buf_size = 1024; + + std::error_code ec; + std::filesystem::file_status tty = std::filesystem::status(tty_file_name, ec); + + if (!ec && exists(tty) && is_character_file(tty) + && (tty.permissions() & std::filesystem::perms::others_write) != std::filesystem::perms::none) + { + tty_buf = std::make_unique(tty_file_name, buf_size); + } + else if (stderr_is_a_tty) + { + tty_buf = std::make_unique(STDERR_FILENO, buf_size); + } + else + need_render_progress = false; + } +} + void ClientBase::updateSuggest(const ASTPtr & ast) { std::vector new_words; @@ -939,13 +967,14 @@ void ClientBase::onProgress(const Progress & value) output_format->onProgress(value); if (need_render_progress) - progress_indication.writeProgress(); + progress_indication.writeProgress(*tty_buf); } void ClientBase::onEndOfStream() { - progress_indication.clearProgressOutput(); + if (need_render_progress) + progress_indication.clearProgressOutput(*tty_buf); if (output_format) output_format->finalize(); @@ -953,10 +982,7 @@ void ClientBase::onEndOfStream() resetOutput(); if (is_interactive && !written_first_block) - { - progress_indication.clearProgressOutput(); std::cout << "Ok." << std::endl; - } } @@ -1000,14 +1026,15 @@ void ClientBase::onProfileEvents(Block & block) progress_indication.updateThreadEventData(thread_times); if (need_render_progress) - progress_indication.writeProgress(); + progress_indication.writeProgress(*tty_buf); if (profile_events.print) { if (profile_events.watch.elapsedMilliseconds() >= profile_events.delay_ms) { initLogsOutputStream(); - progress_indication.clearProgressOutput(); + if (need_render_progress) + progress_indication.clearProgressOutput(*tty_buf); logs_out_stream->writeProfileEvents(block); logs_out_stream->flush(); @@ -1181,7 +1208,7 @@ void ClientBase::sendData(Block & sample, const ColumnsDescription & columns_des progress_indication.updateProgress(Progress(file_progress)); /// Set callback to be called on file progress. - progress_indication.setFileProgressCallback(global_context, true); + progress_indication.setFileProgressCallback(global_context, *tty_buf); } /// If data fetched from file (maybe compressed file) @@ -1433,12 +1460,10 @@ bool ClientBase::receiveEndOfQuery() void ClientBase::cancelQuery() { connection->sendCancel(); - if (is_interactive) - { - progress_indication.clearProgressOutput(); - std::cout << "Cancelling query." << std::endl; + if (need_render_progress) + progress_indication.clearProgressOutput(*tty_buf); - } + std::cout << "Cancelling query." << std::endl; cancelled = true; } @@ -1556,7 +1581,8 @@ void ClientBase::processParsedSingleQuery(const String & full_query, const Strin if (profile_events.last_block) { initLogsOutputStream(); - progress_indication.clearProgressOutput(); + if (need_render_progress) + progress_indication.clearProgressOutput(*tty_buf); logs_out_stream->writeProfileEvents(profile_events.last_block); logs_out_stream->flush(); @@ -2216,6 +2242,8 @@ void ClientBase::init(int argc, char ** argv) stdout_is_a_tty = isatty(STDOUT_FILENO); stderr_is_a_tty = isatty(STDERR_FILENO); terminal_width = getTerminalWidth(); + if (need_render_progress) + initTtyBuffer(); Arguments common_arguments{""}; /// 0th argument is ignored. std::vector external_tables_arguments; @@ -2243,7 +2271,7 @@ void ClientBase::init(int argc, char ** argv) ("stage", po::value()->default_value("complete"), "Request query processing up to specified stage: complete,fetch_columns,with_mergeable_state,with_mergeable_state_after_aggregation,with_mergeable_state_after_aggregation_and_limit") ("query_kind", po::value()->default_value("initial_query"), "One of initial_query/secondary_query/no_query") ("query_id", po::value(), "query_id") - ("progress", "print progress of queries execution") + ("progress", po::value()->implicit_value(true), "print progress of queries execution") ("disable_suggestion,A", "Disable loading suggestion data. Note that suggestion data is loaded asynchronously through a second connection to ClickHouse server. Also it is reasonable to disable suggestion if you want to paste a query with TAB characters. Shorthand option -A is for those who get used to mysql client.") ("time,t", "print query execution time to stderr in non-interactive mode (for benchmarks)") @@ -2348,7 +2376,7 @@ void ClientBase::init(int argc, char ** argv) if (options.count("profile-events-delay-ms")) config().setInt("profile-events-delay-ms", options["profile-events-delay-ms"].as()); if (options.count("progress")) - config().setBool("progress", true); + config().setBool("progress", options["progress"].as()); if (options.count("echo")) config().setBool("echo", true); if (options.count("disable_suggestion")) diff --git a/src/Client/ClientBase.h b/src/Client/ClientBase.h index 3a6e623dc3f..86471405d00 100644 --- a/src/Client/ClientBase.h +++ b/src/Client/ClientBase.h @@ -143,11 +143,11 @@ private: void initOutputFormat(const Block & block, ASTPtr parsed_query); void initLogsOutputStream(); + void initTtyBuffer(); String prompt() const; void resetOutput(); - void outputQueryInfo(bool echo_query_); void parseAndCheckOptions(OptionsDescription & options_description, po::variables_map & options, Arguments & arguments); void updateSuggest(const ASTPtr & ast); @@ -219,6 +219,10 @@ protected: String server_logs_file; std::unique_ptr logs_out_stream; + /// /dev/tty if accessible or std::cerr - for progress bar. + /// We prefer to output progress bar directly to tty to allow user to redirect stdout and stderr and still get the progress indication. + std::unique_ptr tty_buf; + String home_path; String history_file; /// Path to a file containing command history. diff --git a/src/Common/ProgressIndication.cpp b/src/Common/ProgressIndication.cpp index ab4ecf5c25f..7828dc18413 100644 --- a/src/Common/ProgressIndication.cpp +++ b/src/Common/ProgressIndication.cpp @@ -2,6 +2,7 @@ #include #include #include +#include #include #include #include @@ -44,15 +45,6 @@ bool ProgressIndication::updateProgress(const Progress & value) return progress.incrementPiecewiseAtomically(value); } -void ProgressIndication::clearProgressOutput() -{ - if (written_progress_chars) - { - written_progress_chars = 0; - std::cerr << "\r" CLEAR_TO_END_OF_LINE; - } -} - void ProgressIndication::resetProgress() { watch.restart(); @@ -67,15 +59,12 @@ void ProgressIndication::resetProgress() } } -void ProgressIndication::setFileProgressCallback(ContextMutablePtr context, bool write_progress_on_update_) +void ProgressIndication::setFileProgressCallback(ContextMutablePtr context, WriteBuffer & message) { - write_progress_on_update = write_progress_on_update_; context->setFileProgressCallback([&](const FileProgress & file_progress) { progress.incrementPiecewiseAtomically(Progress(file_progress)); - - if (write_progress_on_update) - writeProgress(); + writeProgress(message); }); } @@ -153,13 +142,10 @@ void ProgressIndication::writeFinalProgress() std::cout << ". "; } -void ProgressIndication::writeProgress() +void ProgressIndication::writeProgress(WriteBuffer & message) { std::lock_guard lock(progress_mutex); - /// Output all progress bar commands to stderr at once to avoid flicker. - WriteBufferFromFileDescriptor message(STDERR_FILENO, 1024); - static size_t increment = 0; static const char * indicators[8] = { "\033[1;30m→\033[0m", @@ -318,4 +304,14 @@ void ProgressIndication::writeProgress() message.next(); } +void ProgressIndication::clearProgressOutput(WriteBuffer & message) +{ + if (written_progress_chars) + { + written_progress_chars = 0; + message << "\r" CLEAR_TO_END_OF_LINE; + message.next(); + } +} + } diff --git a/src/Common/ProgressIndication.h b/src/Common/ProgressIndication.h index 4f05f41b9d0..1262a4dc968 100644 --- a/src/Common/ProgressIndication.h +++ b/src/Common/ProgressIndication.h @@ -12,9 +12,12 @@ /// http://en.wikipedia.org/wiki/ANSI_escape_code #define CLEAR_TO_END_OF_LINE "\033[K" + namespace DB { +class WriteBuffer; + struct ThreadEventData { UInt64 time() const noexcept { return user_ms + system_ms; } @@ -30,14 +33,13 @@ using HostToThreadTimesMap = std::unordered_map; class ProgressIndication { public: - /// Write progress to stderr. - void writeProgress(); + /// Write progress bar. + void writeProgress(WriteBuffer & message); + void clearProgressOutput(WriteBuffer & message); + /// Write summary. void writeFinalProgress(); - /// Clear stderr output. - void clearProgressOutput(); - /// Reset progress values. void resetProgress(); @@ -52,7 +54,7 @@ public: /// In some cases there is a need to update progress value, when there is no access to progress_inidcation object. /// In this case it is added via context. /// `write_progress_on_update` is needed to write progress for loading files data via pipe in non-interactive mode. - void setFileProgressCallback(ContextMutablePtr context, bool write_progress_on_update = false); + void setFileProgressCallback(ContextMutablePtr context, WriteBuffer & message); /// How much seconds passed since query execution start. double elapsedSeconds() const { return getElapsedNanoseconds() / 1e9; } From 8b84788d089cbbf46e0a14f69db1f31b9e79e21d Mon Sep 17 00:00:00 2001 From: Alexey Milovidov Date: Sun, 2 Oct 2022 00:38:41 +0200 Subject: [PATCH 039/266] Fix error --- src/Client/ClientBase.cpp | 18 ++++++++++++++++-- 1 file changed, 16 insertions(+), 2 deletions(-) diff --git a/src/Client/ClientBase.cpp b/src/Client/ClientBase.cpp index 566ee49fba3..936456c4c34 100644 --- a/src/Client/ClientBase.cpp +++ b/src/Client/ClientBase.cpp @@ -104,6 +104,7 @@ namespace ErrorCodes extern const int CANNOT_SET_SIGNAL_HANDLER; extern const int UNRECOGNIZED_ARGUMENTS; extern const int LOGICAL_ERROR; + extern const int CANNOT_OPEN_FILE; } } @@ -657,9 +658,22 @@ void ClientBase::initTtyBuffer() if (!ec && exists(tty) && is_character_file(tty) && (tty.permissions() & std::filesystem::perms::others_write) != std::filesystem::perms::none) { - tty_buf = std::make_unique(tty_file_name, buf_size); + try + { + tty_buf = std::make_unique(tty_file_name, buf_size); + return; + } + catch (const Exception & e) + { + if (e.code() != ErrorCodes::CANNOT_OPEN_FILE) + throw; + + /// It is normal if file exists, indicated as writeable but still cannot be opened. + /// Fallback to other options. + } } - else if (stderr_is_a_tty) + + if (stderr_is_a_tty) { tty_buf = std::make_unique(STDERR_FILENO, buf_size); } From 5be163b6467ea482dbfd1f1cfff40b1bae646220 Mon Sep 17 00:00:00 2001 From: Alexey Milovidov Date: Sun, 2 Oct 2022 01:24:19 +0200 Subject: [PATCH 040/266] Fix comment --- src/Client/ClientBase.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Client/ClientBase.cpp b/src/Client/ClientBase.cpp index 936456c4c34..320afb5e0f8 100644 --- a/src/Client/ClientBase.cpp +++ b/src/Client/ClientBase.cpp @@ -648,7 +648,7 @@ void ClientBase::initTtyBuffer() { static constexpr auto tty_file_name = "/dev/tty"; - /// Output all progress bar commands to stderr at once to avoid flicker. + /// Output all progress bar commands to terminal at once to avoid flicker. /// This size is usually greater than the window size. static constexpr size_t buf_size = 1024; From bc473f4ec0d57ce779bfaf669d57e6376225be28 Mon Sep 17 00:00:00 2001 From: Alexey Milovidov Date: Sun, 2 Oct 2022 16:08:33 +0200 Subject: [PATCH 041/266] Something --- src/Client/ClientBase.cpp | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/src/Client/ClientBase.cpp b/src/Client/ClientBase.cpp index 320afb5e0f8..3385a865550 100644 --- a/src/Client/ClientBase.cpp +++ b/src/Client/ClientBase.cpp @@ -661,10 +661,19 @@ void ClientBase::initTtyBuffer() try { tty_buf = std::make_unique(tty_file_name, buf_size); + + /// It is possible that the terminal file has writeable permissions + /// but we cannot write anything there. Check it with invisible character. + tty_buf->write('\0'); + tty_buf->next(); + return; } catch (const Exception & e) { + if (tty_buf) + tty_buf.reset(); + if (e.code() != ErrorCodes::CANNOT_OPEN_FILE) throw; From eef40ce88407e3315c1de0a1348927be9918f621 Mon Sep 17 00:00:00 2001 From: Alexey Milovidov Date: Wed, 5 Oct 2022 02:29:52 +0200 Subject: [PATCH 042/266] Some attempt --- src/Client/ClientBase.cpp | 3 +-- src/Client/ClientBase.h | 4 +++- src/Common/ProgressIndication.cpp | 11 +++++++---- src/Common/ProgressIndication.h | 11 ++++------- 4 files changed, 15 insertions(+), 14 deletions(-) diff --git a/src/Client/ClientBase.cpp b/src/Client/ClientBase.cpp index 3385a865550..52c2294eb7c 100644 --- a/src/Client/ClientBase.cpp +++ b/src/Client/ClientBase.cpp @@ -1,13 +1,11 @@ #include #include -#include #include #include #include #include -#include #include #include #include @@ -66,6 +64,7 @@ #include #include #include +#include #include #include #include diff --git a/src/Client/ClientBase.h b/src/Client/ClientBase.h index 86471405d00..9cc318c2f2b 100644 --- a/src/Client/ClientBase.h +++ b/src/Client/ClientBase.h @@ -15,6 +15,7 @@ #include #include + namespace po = boost::program_options; @@ -38,6 +39,7 @@ enum MultiQueryProcessingStage void interruptSignalHandler(int signum); class InternalTextLogs; +class WriteBufferFromFileDescriptor; class ClientBase : public Poco::Util::Application, public IHints<2, ClientBase> { @@ -221,7 +223,7 @@ protected: /// /dev/tty if accessible or std::cerr - for progress bar. /// We prefer to output progress bar directly to tty to allow user to redirect stdout and stderr and still get the progress indication. - std::unique_ptr tty_buf; + std::unique_ptr tty_buf; String home_path; String history_file; /// Path to a file containing command history. diff --git a/src/Common/ProgressIndication.cpp b/src/Common/ProgressIndication.cpp index 7828dc18413..30604d7808a 100644 --- a/src/Common/ProgressIndication.cpp +++ b/src/Common/ProgressIndication.cpp @@ -12,6 +12,9 @@ #include "IO/WriteBufferFromString.h" #include +/// http://en.wikipedia.org/wiki/ANSI_escape_code +#define CLEAR_TO_END_OF_LINE "\033[K" + namespace { @@ -59,7 +62,7 @@ void ProgressIndication::resetProgress() } } -void ProgressIndication::setFileProgressCallback(ContextMutablePtr context, WriteBuffer & message) +void ProgressIndication::setFileProgressCallback(ContextMutablePtr context, WriteBufferFromFileDescriptor & message) { context->setFileProgressCallback([&](const FileProgress & file_progress) { @@ -142,7 +145,7 @@ void ProgressIndication::writeFinalProgress() std::cout << ". "; } -void ProgressIndication::writeProgress(WriteBuffer & message) +void ProgressIndication::writeProgress(WriteBufferFromFileDescriptor & message) { std::lock_guard lock(progress_mutex); @@ -160,7 +163,7 @@ void ProgressIndication::writeProgress(WriteBuffer & message) const char * indicator = indicators[increment % 8]; - size_t terminal_width = getTerminalWidth(); + size_t terminal_width = getTerminalWidth(message.getFD()); if (!written_progress_chars) { @@ -304,7 +307,7 @@ void ProgressIndication::writeProgress(WriteBuffer & message) message.next(); } -void ProgressIndication::clearProgressOutput(WriteBuffer & message) +void ProgressIndication::clearProgressOutput(WriteBufferFromFileDescriptor & message) { if (written_progress_chars) { diff --git a/src/Common/ProgressIndication.h b/src/Common/ProgressIndication.h index 1262a4dc968..33c77f4fc34 100644 --- a/src/Common/ProgressIndication.h +++ b/src/Common/ProgressIndication.h @@ -9,14 +9,11 @@ #include #include -/// http://en.wikipedia.org/wiki/ANSI_escape_code -#define CLEAR_TO_END_OF_LINE "\033[K" - namespace DB { -class WriteBuffer; +class WriteBufferFromFileDescriptor; struct ThreadEventData { @@ -34,8 +31,8 @@ class ProgressIndication { public: /// Write progress bar. - void writeProgress(WriteBuffer & message); - void clearProgressOutput(WriteBuffer & message); + void writeProgress(WriteBufferFromFileDescriptor & message); + void clearProgressOutput(WriteBufferFromFileDescriptor & message); /// Write summary. void writeFinalProgress(); @@ -54,7 +51,7 @@ public: /// In some cases there is a need to update progress value, when there is no access to progress_inidcation object. /// In this case it is added via context. /// `write_progress_on_update` is needed to write progress for loading files data via pipe in non-interactive mode. - void setFileProgressCallback(ContextMutablePtr context, WriteBuffer & message); + void setFileProgressCallback(ContextMutablePtr context, WriteBufferFromFileDescriptor & message); /// How much seconds passed since query execution start. double elapsedSeconds() const { return getElapsedNanoseconds() / 1e9; } From 68abf43767d6f6e998373094147b7901b6222063 Mon Sep 17 00:00:00 2001 From: Nikolay Degterinsky Date: Sun, 9 Oct 2022 00:00:14 +0000 Subject: [PATCH 043/266] Better INTERVAL parsing and execution --- src/Functions/FunctionBinaryArithmetic.h | 71 ++++++++++++ src/Functions/vectorFunctions.cpp | 105 ++++++++++++++++++ src/Parsers/ExpressionListParsers.cpp | 81 ++++++++------ .../02457_tuple_of_intervals.reference | 17 +++ .../0_stateless/02457_tuple_of_intervals.sql | 21 ++++ 5 files changed, 262 insertions(+), 33 deletions(-) create mode 100644 tests/queries/0_stateless/02457_tuple_of_intervals.reference create mode 100644 tests/queries/0_stateless/02457_tuple_of_intervals.sql diff --git a/src/Functions/FunctionBinaryArithmetic.h b/src/Functions/FunctionBinaryArithmetic.h index 174e98dd81f..399cffac85e 100644 --- a/src/Functions/FunctionBinaryArithmetic.h +++ b/src/Functions/FunctionBinaryArithmetic.h @@ -708,6 +708,41 @@ class FunctionBinaryArithmetic : public IFunction return FunctionFactory::instance().get(function_name, context); } + static FunctionOverloadResolverPtr + getFunctionForTupleOfIntervalsArithmetic(const DataTypePtr & type0, const DataTypePtr & type1, ContextPtr context) + { + bool first_is_date_or_datetime = isDate(type0) || isDateTime(type0) || isDateTime64(type0); + bool second_is_date_or_datetime = isDate(type1) || isDateTime(type1) || isDateTime64(type1); + + /// Exactly one argument must be Date or DateTime + if (first_is_date_or_datetime == second_is_date_or_datetime) + return {}; + + if (!isTuple(type0) && !isTuple(type1)) + return {}; + + /// Special case when the function is plus or minus, one of arguments is Date/DateTime and another is Tuple. + /// We construct another function and call it. + if constexpr (!is_plus && !is_minus) + return {}; + + if (isTuple(type0) && second_is_date_or_datetime && is_minus) + throw Exception("Wrong order of arguments for function " + String(name) + ": argument of Tuple type cannot be first", + ErrorCodes::ILLEGAL_TYPE_OF_ARGUMENT); + + std::string function_name; + if (is_plus) + { + function_name = "addTupleOfIntervals"; + } + else if (is_minus) + { + function_name = "subtractTupleOfIntervals"; + } + + return FunctionFactory::instance().get(function_name, context); + } + static FunctionOverloadResolverPtr getFunctionForTupleArithmetic(const DataTypePtr & type0, const DataTypePtr & type1, ContextPtr context) { @@ -906,6 +941,20 @@ class FunctionBinaryArithmetic : public IFunction return function->execute(new_arguments, result_type, input_rows_count); } + ColumnPtr executeDateTimeTupleOfIntervalsPlusMinus(const ColumnsWithTypeAndName & arguments, const DataTypePtr & result_type, + size_t input_rows_count, const FunctionOverloadResolverPtr & function_builder) const + { + ColumnsWithTypeAndName new_arguments = arguments; + + /// Tuple argument must be second. + if (isTuple(arguments[0].type)) + std::swap(new_arguments[0], new_arguments[1]); + + auto function = function_builder->build(new_arguments); + + return function->execute(new_arguments, result_type, input_rows_count); + } + ColumnPtr executeTupleNumberOperator(const ColumnsWithTypeAndName & arguments, const DataTypePtr & result_type, size_t input_rows_count, const FunctionOverloadResolverPtr & function_builder) const { @@ -1121,6 +1170,22 @@ public: return function->getResultType(); } + /// Special case when the function is plus or minus, one of arguments is Date/DateTime and another is Tuple. + if (auto function_builder = getFunctionForTupleOfIntervalsArithmetic(arguments[0], arguments[1], context)) + { + ColumnsWithTypeAndName new_arguments(2); + + for (size_t i = 0; i < 2; ++i) + new_arguments[i].type = arguments[i]; + + /// Tuple argument must be second. + if (isTuple(new_arguments[0].type)) + std::swap(new_arguments[0], new_arguments[1]); + + auto function = function_builder->build(new_arguments); + return function->getResultType(); + } + /// Special case when the function is multiply or divide, one of arguments is Tuple and another is Number. if (auto function_builder = getFunctionForTupleAndNumberArithmetic(arguments[0], arguments[1], context)) { @@ -1553,6 +1618,12 @@ public: return executeDateTimeIntervalPlusMinus(arguments, result_type, input_rows_count, function_builder); } + /// Special case when the function is plus or minus, one of arguments is Date/DateTime and another is Tuple. + if (auto function_builder = getFunctionForTupleOfIntervalsArithmetic(arguments[0].type, arguments[1].type, context)) + { + return executeDateTimeTupleOfIntervalsPlusMinus(arguments, result_type, input_rows_count, function_builder); + } + /// Special case when the function is plus, minus or multiply, both arguments are tuples. if (auto function_builder = getFunctionForTupleArithmetic(arguments[0].type, arguments[1].type, context)) { diff --git a/src/Functions/vectorFunctions.cpp b/src/Functions/vectorFunctions.cpp index 20571f67eff..4e9f2a71f8c 100644 --- a/src/Functions/vectorFunctions.cpp +++ b/src/Functions/vectorFunctions.cpp @@ -415,6 +415,108 @@ public: } }; +template +class FunctionDateOrDateTimeOperationTupleOfIntervals : public ITupleFunction +{ +public: + static constexpr auto name = Impl::name; + + explicit FunctionDateOrDateTimeOperationTupleOfIntervals(ContextPtr context_) : ITupleFunction(context_) {} + static FunctionPtr create(ContextPtr context_) + { + return std::make_shared(context_); + } + + String getName() const override { return name; } + + size_t getNumberOfArguments() const override { return 2; } + + DataTypePtr getReturnTypeImpl(const ColumnsWithTypeAndName & arguments) const override + { + if (!isDate(arguments[0].type) && !isDate32(arguments[0].type) && !isDateTime(arguments[0].type) && !isDateTime64(arguments[0].type)) + throw Exception{ErrorCodes::ILLEGAL_TYPE_OF_ARGUMENT, + "Illegal type {} of first argument of function {}. Should be a date or a date with time", + arguments[0].type->getName(), getName()}; + + const auto * cur_tuple = checkAndGetDataType(arguments[1].type.get()); + + if (!cur_tuple) + throw Exception{ErrorCodes::ILLEGAL_TYPE_OF_ARGUMENT, + "Illegal type {} of second argument of function {}. Should be a tuple", + arguments[0].type->getName(), getName()}; + + const auto & cur_types = cur_tuple->getElements(); + + Columns cur_elements; + if (arguments[1].column) + cur_elements = getTupleElements(*arguments[1].column); + + size_t tuple_size = cur_types.size(); + if (tuple_size == 0) + return arguments[0].type; + + auto plus = FunctionFactory::instance().get(Impl::func_name, context); + DataTypePtr res_type = arguments[0].type; + for (size_t i = 0; i < tuple_size; ++i) + { + try + { + ColumnWithTypeAndName left{res_type, {}}; + ColumnWithTypeAndName right{cur_elements.empty() ? nullptr : cur_elements[i], cur_types[i], {}}; + auto plus_elem = plus->build({left, right}); + res_type = plus_elem->getResultType(); + } + catch (DB::Exception & e) + { + e.addMessage("While executing function {} for tuple element {}", getName(), i); + throw; + } + } + + return res_type; + } + + ColumnPtr executeImpl(const ColumnsWithTypeAndName & arguments, const DataTypePtr &, size_t input_rows_count) const override + { + const auto * cur_tuple = checkAndGetDataType(arguments[1].type.get()); + const auto & cur_types = cur_tuple->getElements(); + auto cur_elements = getTupleElements(*arguments[1].column); + + size_t tuple_size = cur_elements.size(); + if (tuple_size == 0) + return arguments[0].column; + + auto plus = FunctionFactory::instance().get(Impl::func_name, context); + ColumnWithTypeAndName res; + for (size_t i = 0; i < tuple_size; ++i) + { + ColumnWithTypeAndName column{cur_elements[i], cur_types[i], {}}; + auto elem_plus = plus->build(ColumnsWithTypeAndName{i == 0 ? arguments[0] : res, column}); + auto res_type = elem_plus->getResultType(); + res.column = elem_plus->execute({i == 0 ? arguments[0] : res, column}, res_type, input_rows_count); + res.type = res_type; + } + + return res.column; + } +}; + +struct AddTupleOfIntervalsImpl +{ + static constexpr auto name = "addTupleOfIntervals"; + static constexpr auto func_name = "plus"; +}; + +struct SubtractTupleOfIntervalsImpl +{ + static constexpr auto name = "subtractTupleOfIntervals"; + static constexpr auto func_name = "minus"; +}; + +using FunctionAddTupleOfIntervals = FunctionDateOrDateTimeOperationTupleOfIntervals; + +using FunctionSubtractTupleOfIntervals = FunctionDateOrDateTimeOperationTupleOfIntervals; + /// this is for convenient usage in LNormalize template class FunctionLNorm : public ITupleFunction {}; @@ -1282,6 +1384,9 @@ REGISTER_FUNCTION(VectorFunctions) factory.registerFunction(); factory.registerFunction(); + factory.registerFunction(); + factory.registerFunction(); + factory.registerFunction(); factory.registerFunction(); diff --git a/src/Parsers/ExpressionListParsers.cpp b/src/Parsers/ExpressionListParsers.cpp index f7a016a59e4..353f22b03b6 100644 --- a/src/Parsers/ExpressionListParsers.cpp +++ b/src/Parsers/ExpressionListParsers.cpp @@ -1684,6 +1684,16 @@ private: class IntervalLayer : public Layer { public: + bool getResult(ASTPtr & node) override + { + if (elements.size() == 1) + node = elements[0]; + else + node = makeASTFunction("tuple", std::move(elements)); + + return true; + } + bool parse(IParser::Pos & pos, Expected & expected, Action & action) override { /// INTERVAL 1 HOUR or INTERVAL expr HOUR @@ -1693,49 +1703,54 @@ public: if (state == 0) { + state = 1; + auto begin = pos; auto init_expected = expected; ASTPtr string_literal; + String literal; + //// A String literal followed INTERVAL keyword, /// the literal can be a part of an expression or /// include Number and INTERVAL TYPE at the same time - if (ParserStringLiteral{}.parse(pos, string_literal, expected)) + if (ParserStringLiteral{}.parse(pos, string_literal, expected) + && string_literal->as().value.tryGet(literal)) { - String literal; - if (string_literal->as().value.tryGet(literal)) + Tokens tokens(literal.data(), literal.data() + literal.size()); + IParser::Pos token_pos(tokens, 0); + Expected token_expected; + ASTPtr expr; + + if (!ParserNumber{}.parse(token_pos, expr, token_expected)) + return false; + + /// case: INTERVAL '1' HOUR + /// back to begin + if (!token_pos.isValid()) { - Tokens tokens(literal.data(), literal.data() + literal.size()); - IParser::Pos token_pos(tokens, 0); - Expected token_expected; - ASTPtr expr; - - if (!ParserNumber{}.parse(token_pos, expr, token_expected)) - { - return false; - } - else - { - /// case: INTERVAL '1' HOUR - /// back to begin - if (!token_pos.isValid()) - { - pos = begin; - expected = init_expected; - } - else - { - /// case: INTERVAL '1 HOUR' - if (!parseIntervalKind(token_pos, token_expected, interval_kind)) - return false; - - elements = {makeASTFunction(interval_kind.toNameOfFunctionToIntervalDataType(), expr)}; - finished = true; - return true; - } - } + pos = begin; + expected = init_expected; + return true; } + + /// case: INTERVAL '1 HOUR' + if (!parseIntervalKind(token_pos, token_expected, interval_kind)) + return false; + + pushResult(makeASTFunction(interval_kind.toNameOfFunctionToIntervalDataType(), expr)); + + /// case: INTERVAL '1 HOUR 1 SECOND ...' + while (token_pos.isValid()) + { + if (!ParserNumber{}.parse(token_pos, expr, token_expected) || + !parseIntervalKind(token_pos, token_expected, interval_kind)) + return false; + + pushResult(makeASTFunction(interval_kind.toNameOfFunctionToIntervalDataType(), expr)); + } + + finished = true; } - state = 1; return true; } diff --git a/tests/queries/0_stateless/02457_tuple_of_intervals.reference b/tests/queries/0_stateless/02457_tuple_of_intervals.reference new file mode 100644 index 00000000000..40bbfb35d91 --- /dev/null +++ b/tests/queries/0_stateless/02457_tuple_of_intervals.reference @@ -0,0 +1,17 @@ +SELECT (toIntervalSecond(-1), toIntervalMinute(2), toIntervalMonth(-3), toIntervalYear(1)) +- +2022-11-12 +2022-11-12 +2022-11-12 +- +2023-07-11 00:01:59 +2023-07-11 00:01:59 +2023-07-11 00:01:59 +- +2021-07-31 23:00:00 +2021-07-31 23:00:00 +2021-07-31 23:00:00 +- +2021-06-10 23:59:59.000 +2021-06-10 23:59:59.000 +2021-06-10 23:59:59.000 diff --git a/tests/queries/0_stateless/02457_tuple_of_intervals.sql b/tests/queries/0_stateless/02457_tuple_of_intervals.sql new file mode 100644 index 00000000000..2c2feaf522a --- /dev/null +++ b/tests/queries/0_stateless/02457_tuple_of_intervals.sql @@ -0,0 +1,21 @@ +EXPLAIN SYNTAX SELECT INTERVAL '-1 SECOND 2 MINUTE -3 MONTH 1 YEAR'; + +SELECT '-'; +SELECT '2022-10-11'::Date + INTERVAL 1 DAY + INTERVAL 1 MONTH; +SELECT '2022-10-11'::Date + (INTERVAL 1 DAY, INTERVAL 1 MONTH); +SELECT '2022-10-11'::Date + INTERVAL '1 DAY 1 MONTH'; + +SELECT '-'; +SELECT '2022-10-11'::Date + INTERVAL -1 SECOND + INTERVAL 2 MINUTE + INTERVAL -3 MONTH + INTERVAL 1 YEAR; +SELECT '2022-10-11'::Date + (INTERVAL -1 SECOND, INTERVAL 2 MINUTE, INTERVAL -3 MONTH, INTERVAL 1 YEAR); +SELECT '2022-10-11'::Date + INTERVAL '-1 SECOND 2 MINUTE -3 MONTH 1 YEAR'; + +SELECT '-'; +SELECT '2022-10-11'::DateTime - INTERVAL 1 QUARTER - INTERVAL -3 WEEK - INTERVAL 1 YEAR - INTERVAL 1 HOUR; +SELECT '2022-10-11'::DateTime - (INTERVAL 1 QUARTER, INTERVAL -3 WEEK, INTERVAL 1 YEAR, INTERVAL 1 HOUR); +SELECT '2022-10-11'::DateTime - INTERVAL '1 QUARTER -3 WEEK 1 YEAR 1 HOUR'; + +SELECT '-'; +SELECT '2022-10-11'::DateTime64 - INTERVAL 1 YEAR - INTERVAL 4 MONTH - INTERVAL 1 SECOND; +SELECT '2022-10-11'::DateTime64 - (INTERVAL 1 YEAR, INTERVAL 4 MONTH, INTERVAL 1 SECOND); +SELECT '2022-10-11'::DateTime64 - INTERVAL '1 YEAR 4 MONTH 1 SECOND'; From f823b9a177e77ee1a6c25f6779c13f9e031c662b Mon Sep 17 00:00:00 2001 From: Duc Canh Le Date: Mon, 10 Oct 2022 13:31:10 +0800 Subject: [PATCH 044/266] add not like to atom map --- src/Storages/MergeTree/KeyCondition.cpp | 21 +++++++++++++++++++ ...58_key_condition_not_like_prefix.reference | 1 + .../002458_key_condition_not_like_prefix.sql | 5 +++++ 3 files changed, 27 insertions(+) create mode 100644 tests/queries/0_stateless/002458_key_condition_not_like_prefix.reference create mode 100644 tests/queries/0_stateless/002458_key_condition_not_like_prefix.sql diff --git a/src/Storages/MergeTree/KeyCondition.cpp b/src/Storages/MergeTree/KeyCondition.cpp index 24b3a4a60b9..ccdd26eb333 100644 --- a/src/Storages/MergeTree/KeyCondition.cpp +++ b/src/Storages/MergeTree/KeyCondition.cpp @@ -560,6 +560,27 @@ const KeyCondition::AtomMap KeyCondition::atom_map return true; } }, + { + "notLike", + [] (RPNElement & out, const Field & value) + { + if (value.getType() != Field::Types::String) + return false; + + String prefix = extractFixedPrefixFromLikePattern(value.get()); + if (prefix.empty()) + return false; + + String right_bound = firstStringThatIsGreaterThanAllStringsWithPrefix(prefix); + + out.function = RPNElement::FUNCTION_NOT_IN_RANGE; + out.range = !right_bound.empty() + ? Range(prefix, true, right_bound, false) + : Range::createLeftBounded(prefix, true); + + return true; + } + }, { "startsWith", [] (RPNElement & out, const Field & value) diff --git a/tests/queries/0_stateless/002458_key_condition_not_like_prefix.reference b/tests/queries/0_stateless/002458_key_condition_not_like_prefix.reference new file mode 100644 index 00000000000..87766d889a3 --- /dev/null +++ b/tests/queries/0_stateless/002458_key_condition_not_like_prefix.reference @@ -0,0 +1 @@ +200000 diff --git a/tests/queries/0_stateless/002458_key_condition_not_like_prefix.sql b/tests/queries/0_stateless/002458_key_condition_not_like_prefix.sql new file mode 100644 index 00000000000..211fa5662e7 --- /dev/null +++ b/tests/queries/0_stateless/002458_key_condition_not_like_prefix.sql @@ -0,0 +1,5 @@ +CREATE TABLE data (str String) ENGINE=MergeTree ORDER BY str; +INSERT INTO data (str) SELECT 'aa' FROM numbers(100000); +INSERT INTO data (str) SELECT 'ba' FROM numbers(100000); +INSERT INTO data (str) SELECT 'ca' FROM numbers(100000); +SELECT count()FROM data WHERE str NOT LIKE 'a%' SETTINGS force_primary_key=1; \ No newline at end of file From 0a702d9520996f0b3e975b27169f1f9b827c0b19 Mon Sep 17 00:00:00 2001 From: kssenii Date: Mon, 10 Oct 2022 17:23:09 +0200 Subject: [PATCH 045/266] Fix --- src/IO/ReadWriteBufferFromHTTP.h | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/src/IO/ReadWriteBufferFromHTTP.h b/src/IO/ReadWriteBufferFromHTTP.h index de2b5654ae5..b60fdee1184 100644 --- a/src/IO/ReadWriteBufferFromHTTP.h +++ b/src/IO/ReadWriteBufferFromHTTP.h @@ -528,16 +528,17 @@ namespace detail auto on_retriable_error = [&]() { - retry_with_range_header = true; - impl.reset(); - auto http_session = session->getSession(); - http_session->reset(); - sleepForMilliseconds(milliseconds_to_wait); + retry_with_range_header = true; + impl.reset(); + auto http_session = session->getSession(); + http_session->reset(); + sleepForMilliseconds(milliseconds_to_wait); }; for (size_t i = 0; i < settings.http_max_tries; ++i) { exception = nullptr; + initialization_error = InitializeError::NONE; try { From b724b7a74a2b88e10ef08a6aacc32b64a38105cf Mon Sep 17 00:00:00 2001 From: lixuchun Date: Wed, 12 Oct 2022 11:57:35 +0800 Subject: [PATCH 046/266] update docs error --- docs/en/engines/database-engines/replicated.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/en/engines/database-engines/replicated.md b/docs/en/engines/database-engines/replicated.md index f0ef1e981fe..43d1ce5ec3f 100644 --- a/docs/en/engines/database-engines/replicated.md +++ b/docs/en/engines/database-engines/replicated.md @@ -86,7 +86,7 @@ node1 :) SELECT materialize(hostName()) AS host, groupArray(n) FROM r.d GROUP BY ``` text ┌─hosts─┬─groupArray(n)─┐ -│ node1 │ [1,3,5,7,9] │ +│ node3 │ [1,3,5,7,9] │ │ node2 │ [0,2,4,6,8] │ └───────┴───────────────┘ ``` From af1d306b12756e3c2f5d5de4bb7df0086c95ba77 Mon Sep 17 00:00:00 2001 From: Nikolay Degterinsky Date: Fri, 14 Oct 2022 02:16:12 +0000 Subject: [PATCH 047/266] Add Interval arithmetics --- src/DataTypes/IDataType.h | 1 + src/Functions/FunctionBinaryArithmetic.h | 100 +++++++- src/Functions/FunctionUnaryArithmetic.h | 10 +- src/Functions/vectorFunctions.cpp | 227 +++++++++++++++++- .../02457_tuple_of_intervals.reference | 31 ++- .../0_stateless/02457_tuple_of_intervals.sql | 53 ++-- 6 files changed, 380 insertions(+), 42 deletions(-) diff --git a/src/DataTypes/IDataType.h b/src/DataTypes/IDataType.h index c93128ced95..45353796f3c 100644 --- a/src/DataTypes/IDataType.h +++ b/src/DataTypes/IDataType.h @@ -408,6 +408,7 @@ inline bool isDecimal(const DataTypePtr & data_type) { return WhichDataType(data inline bool isTuple(const DataTypePtr & data_type) { return WhichDataType(data_type).isTuple(); } inline bool isArray(const DataTypePtr & data_type) { return WhichDataType(data_type).isArray(); } inline bool isMap(const DataTypePtr & data_type) {return WhichDataType(data_type).isMap(); } +inline bool isInterval(const DataTypePtr & data_type) {return WhichDataType(data_type).isInterval(); } inline bool isNothing(const DataTypePtr & data_type) { return WhichDataType(data_type).isNothing(); } inline bool isUUID(const DataTypePtr & data_type) { return WhichDataType(data_type).isUUID(); } diff --git a/src/Functions/FunctionBinaryArithmetic.h b/src/Functions/FunctionBinaryArithmetic.h index 399cffac85e..e4919d3e9d7 100644 --- a/src/Functions/FunctionBinaryArithmetic.h +++ b/src/Functions/FunctionBinaryArithmetic.h @@ -22,6 +22,7 @@ #include #include #include +#include #include #include #include @@ -633,7 +634,8 @@ class FunctionBinaryArithmetic : public IFunction DataTypeInt8, DataTypeInt16, DataTypeInt32, DataTypeInt64, DataTypeInt128, DataTypeInt256, DataTypeDecimal32, DataTypeDecimal64, DataTypeDecimal128, DataTypeDecimal256, DataTypeDate, DataTypeDateTime, - DataTypeFixedString, DataTypeString>; + DataTypeFixedString, DataTypeString, + DataTypeInterval>; using Floats = TypeList; @@ -709,10 +711,10 @@ class FunctionBinaryArithmetic : public IFunction } static FunctionOverloadResolverPtr - getFunctionForTupleOfIntervalsArithmetic(const DataTypePtr & type0, const DataTypePtr & type1, ContextPtr context) + getFunctionForDateTupleOfIntervalsArithmetic(const DataTypePtr & type0, const DataTypePtr & type1, ContextPtr context) { - bool first_is_date_or_datetime = isDate(type0) || isDateTime(type0) || isDateTime64(type0); - bool second_is_date_or_datetime = isDate(type1) || isDateTime(type1) || isDateTime64(type1); + bool first_is_date_or_datetime = isDateOrDate32(type0) || isDateTime(type0) || isDateTime64(type0); + bool second_is_date_or_datetime = isDateOrDate32(type1) || isDateTime(type1) || isDateTime64(type1); /// Exactly one argument must be Date or DateTime if (first_is_date_or_datetime == second_is_date_or_datetime) @@ -735,7 +737,7 @@ class FunctionBinaryArithmetic : public IFunction { function_name = "addTupleOfIntervals"; } - else if (is_minus) + else { function_name = "subtractTupleOfIntervals"; } @@ -743,6 +745,47 @@ class FunctionBinaryArithmetic : public IFunction return FunctionFactory::instance().get(function_name, context); } + static FunctionOverloadResolverPtr + getFunctionForMergeIntervalsArithmetic(const DataTypePtr & type0, const DataTypePtr & type1, ContextPtr context) + { + /// Special case when the function is plus or minus, first argument is Interval or Tuple of Intervals + /// and the second argument is the Inteval of a different kind. + /// We construct another function (example: addIntervals) and call it + + if constexpr (!is_plus && !is_minus) + return {}; + + const auto * tuple_data_type_0 = checkAndGetDataType(type0.get()); + const auto * interval_data_type_0 = checkAndGetDataType(type0.get()); + const auto * interval_data_type_1 = checkAndGetDataType(type1.get()); + + if ((!tuple_data_type_0 && !interval_data_type_0) || !interval_data_type_1) + return {}; + + if (interval_data_type_0 && interval_data_type_0->equals(*interval_data_type_1)) + return {}; + + if (tuple_data_type_0) + { + auto & tuple_types = tuple_data_type_0->getElements(); + for (auto & type : tuple_types) + if (!isInterval(type)) + return {}; + } + + std::string function_name; + if (is_plus) + { + function_name = "addInterval"; + } + else + { + function_name = "subtractInterval"; + } + + return FunctionFactory::instance().get(function_name, context); + } + static FunctionOverloadResolverPtr getFunctionForTupleArithmetic(const DataTypePtr & type0, const DataTypePtr & type1, ContextPtr context) { @@ -955,6 +998,16 @@ class FunctionBinaryArithmetic : public IFunction return function->execute(new_arguments, result_type, input_rows_count); } + ColumnPtr executeIntervalTupleOfIntervalsPlusMinus(const ColumnsWithTypeAndName & arguments, const DataTypePtr & result_type, + size_t input_rows_count, const FunctionOverloadResolverPtr & function_builder) const + { + ColumnsWithTypeAndName new_arguments = arguments; + + auto function = function_builder->build(new_arguments); + + return function->execute(new_arguments, result_type, input_rows_count); + } + ColumnPtr executeTupleNumberOperator(const ColumnsWithTypeAndName & arguments, const DataTypePtr & result_type, size_t input_rows_count, const FunctionOverloadResolverPtr & function_builder) const { @@ -1171,7 +1224,7 @@ public: } /// Special case when the function is plus or minus, one of arguments is Date/DateTime and another is Tuple. - if (auto function_builder = getFunctionForTupleOfIntervalsArithmetic(arguments[0], arguments[1], context)) + if (auto function_builder = getFunctionForDateTupleOfIntervalsArithmetic(arguments[0], arguments[1], context)) { ColumnsWithTypeAndName new_arguments(2); @@ -1186,6 +1239,18 @@ public: return function->getResultType(); } + /// Special case when the function is plus or minus, one of arguments is Interval/Tuple of Intervals and another is Interval. + if (auto function_builder = getFunctionForMergeIntervalsArithmetic(arguments[0], arguments[1], context)) + { + ColumnsWithTypeAndName new_arguments(2); + + for (size_t i = 0; i < 2; ++i) + new_arguments[i].type = arguments[i]; + + auto function = function_builder->build(new_arguments); + return function->getResultType(); + } + /// Special case when the function is multiply or divide, one of arguments is Tuple and another is Number. if (auto function_builder = getFunctionForTupleAndNumberArithmetic(arguments[0], arguments[1], context)) { @@ -1237,6 +1302,21 @@ public: type_res = std::make_shared(); return true; } + else if constexpr (std::is_same_v || std::is_same_v) + { + if constexpr (std::is_same_v && + std::is_same_v) + { + if constexpr (is_plus || is_minus) + { + if (left.getKind() == right.getKind()) + { + type_res = std::make_shared(left.getKind()); + return true; + } + } + } + } else { using ResultDataType = typename BinaryOperationTraits::ResultDataType; @@ -1619,11 +1699,17 @@ public: } /// Special case when the function is plus or minus, one of arguments is Date/DateTime and another is Tuple. - if (auto function_builder = getFunctionForTupleOfIntervalsArithmetic(arguments[0].type, arguments[1].type, context)) + if (auto function_builder = getFunctionForDateTupleOfIntervalsArithmetic(arguments[0].type, arguments[1].type, context)) { return executeDateTimeTupleOfIntervalsPlusMinus(arguments, result_type, input_rows_count, function_builder); } + /// Special case when the function is plus or minus, one of arguments is Interval/Tuple of Intervals and another is Interval. + if (auto function_builder = getFunctionForMergeIntervalsArithmetic(arguments[0].type, arguments[1].type, context)) + { + return executeIntervalTupleOfIntervalsPlusMinus(arguments, result_type, input_rows_count, function_builder); + } + /// Special case when the function is plus, minus or multiply, both arguments are tuples. if (auto function_builder = getFunctionForTupleArithmetic(arguments[0].type, arguments[1].type, context)) { diff --git a/src/Functions/FunctionUnaryArithmetic.h b/src/Functions/FunctionUnaryArithmetic.h index 445eb45fd9d..f5ddc5cb67c 100644 --- a/src/Functions/FunctionUnaryArithmetic.h +++ b/src/Functions/FunctionUnaryArithmetic.h @@ -3,6 +3,7 @@ #include #include #include +#include #include #include #include @@ -145,7 +146,8 @@ class FunctionUnaryArithmetic : public IFunction DataTypeDecimal, DataTypeDecimal, DataTypeDecimal, - DataTypeFixedString + DataTypeFixedString, + DataTypeInterval >(type, std::forward(f)); } @@ -211,6 +213,12 @@ public: return false; result = std::make_shared(type.getN()); } + else if constexpr (std::is_same_v) + { + if constexpr (!IsUnaryOperation::negate) + return false; + result = std::make_shared(type.getKind()); + } else { using T0 = typename DataType::FieldType; diff --git a/src/Functions/vectorFunctions.cpp b/src/Functions/vectorFunctions.cpp index 4e9f2a71f8c..007875a8b81 100644 --- a/src/Functions/vectorFunctions.cpp +++ b/src/Functions/vectorFunctions.cpp @@ -1,5 +1,6 @@ #include #include +#include #include #include #include @@ -517,6 +518,172 @@ using FunctionAddTupleOfIntervals = FunctionDateOrDateTimeOperationTupleOfInterv using FunctionSubtractTupleOfIntervals = FunctionDateOrDateTimeOperationTupleOfIntervals; +template +struct FunctionTupleOperationInterval : public ITupleFunction +{ +public: + static constexpr auto name = is_minus ? "subtractInterval" : "addInterval"; + + explicit FunctionTupleOperationInterval(ContextPtr context_) : ITupleFunction(context_) {} + + static FunctionPtr create(ContextPtr context_) + { + return std::make_shared(context_); + } + + String getName() const override { return name; } + + size_t getNumberOfArguments() const override { return 2; } + + DataTypePtr getReturnTypeImpl(const DataTypes & arguments) const override + { + if (!isTuple(arguments[0]) && !isInterval(arguments[0])) + throw Exception(ErrorCodes::ILLEGAL_TYPE_OF_ARGUMENT, + "Illegal type {} of first argument of function {}, must be Tuple or Interval", + arguments[0]->getName(), getName()); + + if (!isInterval(arguments[1])) + throw Exception(ErrorCodes::ILLEGAL_TYPE_OF_ARGUMENT, + "Illegal type {} of second argument of function {}, must be Interval", + arguments[0]->getName(), getName()); + + DataTypes types; + + const auto * tuple = checkAndGetDataType(arguments[0].get()); + + if (tuple) + { + const auto & cur_types = tuple->getElements(); + + for (auto & type : cur_types) + if (!isInterval(type)) + throw Exception(ErrorCodes::ILLEGAL_TYPE_OF_ARGUMENT, + "Illegal type {} of Tuple element of first argument of function {}, must be Interval", + types.back()->getName(), getName()); + + types = cur_types; + } + else + { + types = {arguments[0]}; + } + + const auto * interval_last = checkAndGetDataType(types.back().get()); + const auto * interval_new = checkAndGetDataType(arguments[1].get()); + + if (!interval_last->equals(*interval_new)) + types.push_back(arguments[1]); + + return std::make_shared(types); + } + + ColumnPtr executeImpl(const ColumnsWithTypeAndName & arguments, const DataTypePtr &, size_t input_rows_count) const override + { + if (!isInterval(arguments[1].type)) + throw Exception(ErrorCodes::ILLEGAL_TYPE_OF_ARGUMENT, + "Illegal type {} of second argument of function {}, must be Interval", + arguments[0].type->getName(), getName()); + + Columns tuple_columns; + + const auto * first_tuple = checkAndGetDataType(arguments[0].type.get()); + const auto * first_interval = checkAndGetDataType(arguments[0].type.get()); + const auto * second_interval = checkAndGetDataType(arguments[1].type.get()); + + bool can_be_merged; + + if (first_interval) + { + can_be_merged = first_interval->equals(*second_interval); + + if (can_be_merged) + tuple_columns.resize(1); + else + tuple_columns.resize(2); + + tuple_columns[0] = arguments[0].column->convertToFullColumnIfConst(); + } + else if (first_tuple) + { + const auto & cur_types = first_tuple->getElements(); + + for (auto & type : cur_types) + if (!isInterval(type)) + throw Exception(ErrorCodes::ILLEGAL_TYPE_OF_ARGUMENT, + "Illegal type {} of Tuple element of first argument of function {}, must be Interval", + type->getName(), getName()); + + auto cur_elements = getTupleElements(*arguments[0].column); + size_t tuple_size = cur_elements.size(); + + if (tuple_size == 0) + { + can_be_merged = false; + } + else + { + const auto * tuple_last_interval = checkAndGetDataType(cur_types.back().get()); + can_be_merged = tuple_last_interval->equals(*second_interval); + } + + if (can_be_merged) + tuple_columns.resize(tuple_size); + else + tuple_columns.resize(tuple_size + 1); + + for (size_t i = 0; i < tuple_size; ++i) + tuple_columns[i] = cur_elements[i]; + } + else + throw Exception(ErrorCodes::ILLEGAL_TYPE_OF_ARGUMENT, + "Illegal type {} of first argument of function {}, must be Tuple or Interval", + arguments[0].type->getName(), getName()); + + + ColumnPtr & last_column = tuple_columns.back(); + + if (can_be_merged) + { + ColumnWithTypeAndName left{last_column, arguments[1].type, {}}; + + if constexpr (is_minus) + { + auto minus = FunctionFactory::instance().get("minus", context); + auto elem_minus = minus->build({left, arguments[1]}); + last_column = elem_minus->execute({left, arguments[1]}, arguments[1].type, input_rows_count) + ->convertToFullColumnIfConst(); + } + else + { + auto plus = FunctionFactory::instance().get("plus", context); + auto elem_plus = plus->build({left, arguments[1]}); + last_column = elem_plus->execute({left, arguments[1]}, arguments[1].type, input_rows_count) + ->convertToFullColumnIfConst(); + } + } + else + { + if constexpr (is_minus) + { + auto negate = FunctionFactory::instance().get("negate", context); + auto elem_negate = negate->build({arguments[1]}); + last_column = elem_negate->execute({arguments[1]}, arguments[1].type, input_rows_count); + } + else + { + last_column = arguments[1].column; + } + } + + return ColumnTuple::create(tuple_columns); + } +}; + +using FunctionTupleAddInterval = FunctionTupleOperationInterval; + +using FunctionTupleSubtractInterval = FunctionTupleOperationInterval; + + /// this is for convenient usage in LNormalize template class FunctionLNorm : public ITupleFunction {}; @@ -1384,8 +1551,64 @@ REGISTER_FUNCTION(VectorFunctions) factory.registerFunction(); factory.registerFunction(); - factory.registerFunction(); - factory.registerFunction(); + factory.registerFunction( + { + R"( +Consecutively adds a tuple of intervals to a Date or a DateTime. +[example:tuple] +)", + Documentation::Examples{ + {"tuple", "WITH toDate('2018-01-01') AS date SELECT addTupleOfIntervals(date, (INTERVAL 1 DAY, INTERVAL 1 YEAR))"}, + }, + Documentation::Categories{"Tuple", "Interval", "Date", "DateTime"} + }); + + factory.registerFunction( + { + R"( +Consecutively subtracts a tuple of intervals from a Date or a DateTime. +[example:tuple] +)", + Documentation::Examples{ + {"tuple", "WITH toDate('2018-01-01') AS date SELECT subtractTupleOfIntervals(date, (INTERVAL 1 DAY, INTERVAL 1 YEAR))"}, + }, + Documentation::Categories{"Tuple", "Interval", "Date", "DateTime"} + }); + + factory.registerFunction( + { + R"( +Adds an interval to another interval or tuple of intervals. The returned value is tuple of intervals. +[example:tuple] +[example:interval1] + +If the types of the first interval (or the interval in the tuple) and the second interval are the same they will be merged into one interval. +[example:interval2] +)", + Documentation::Examples{ + {"tuple", "SELECT addInterval((INTERVAL 1 DAY, INTERVAL 1 YEAR), INTERVAL 1 MONTH)"}, + {"interval1", "SELECT addInterval(INTERVAL 1 DAY, INTERVAL 1 MONTH)"}, + {"interval2", "SELECT addInterval(INTERVAL 1 DAY, INTERVAL 1 DAY)"}, + }, + Documentation::Categories{"Tuple", "Interval"} + }); + factory.registerFunction( + { + R"( +Adds an negated interval to another interval or tuple of intervals. The returned value is tuple of intervals. +[example:tuple] +[example:interval1] + +If the types of the first interval (or the interval in the tuple) and the second interval are the same they will be merged into one interval. +[example:interval2] +)", + Documentation::Examples{ + {"tuple", "SELECT subtractInterval((INTERVAL 1 DAY, INTERVAL 1 YEAR), INTERVAL 1 MONTH)"}, + {"interval1", "SELECT subtractInterval(INTERVAL 1 DAY, INTERVAL 1 MONTH)"}, + {"interval2", "SELECT subtractInterval(INTERVAL 2 DAY, INTERVAL 1 DAY)"}, + }, + Documentation::Categories{"Tuple", "Interval"} + }); factory.registerFunction(); factory.registerFunction(); diff --git a/tests/queries/0_stateless/02457_tuple_of_intervals.reference b/tests/queries/0_stateless/02457_tuple_of_intervals.reference index 40bbfb35d91..dd190dce891 100644 --- a/tests/queries/0_stateless/02457_tuple_of_intervals.reference +++ b/tests/queries/0_stateless/02457_tuple_of_intervals.reference @@ -1,17 +1,16 @@ SELECT (toIntervalSecond(-1), toIntervalMinute(2), toIntervalMonth(-3), toIntervalYear(1)) -- -2022-11-12 -2022-11-12 -2022-11-12 -- -2023-07-11 00:01:59 -2023-07-11 00:01:59 -2023-07-11 00:01:59 -- -2021-07-31 23:00:00 -2021-07-31 23:00:00 -2021-07-31 23:00:00 -- -2021-06-10 23:59:59.000 -2021-06-10 23:59:59.000 -2021-06-10 23:59:59.000 +--- +3 IntervalSecond +(1,2) Tuple(IntervalHour, IntervalSecond) +(1,1,1) Tuple(IntervalSecond, IntervalHour, IntervalSecond) +(2,1) Tuple(IntervalSecond, IntervalHour) +--- +-3 IntervalSecond +(-1,-2) Tuple(IntervalHour, IntervalSecond) +(-1,-1,-1) Tuple(IntervalSecond, IntervalHour, IntervalSecond) +(-2,-1) Tuple(IntervalSecond, IntervalHour) +--- +1 +1 +1 +1 diff --git a/tests/queries/0_stateless/02457_tuple_of_intervals.sql b/tests/queries/0_stateless/02457_tuple_of_intervals.sql index 2c2feaf522a..d4065ab98f8 100644 --- a/tests/queries/0_stateless/02457_tuple_of_intervals.sql +++ b/tests/queries/0_stateless/02457_tuple_of_intervals.sql @@ -1,21 +1,42 @@ EXPLAIN SYNTAX SELECT INTERVAL '-1 SECOND 2 MINUTE -3 MONTH 1 YEAR'; -SELECT '-'; -SELECT '2022-10-11'::Date + INTERVAL 1 DAY + INTERVAL 1 MONTH; -SELECT '2022-10-11'::Date + (INTERVAL 1 DAY, INTERVAL 1 MONTH); -SELECT '2022-10-11'::Date + INTERVAL '1 DAY 1 MONTH'; +SELECT '---'; -SELECT '-'; -SELECT '2022-10-11'::Date + INTERVAL -1 SECOND + INTERVAL 2 MINUTE + INTERVAL -3 MONTH + INTERVAL 1 YEAR; -SELECT '2022-10-11'::Date + (INTERVAL -1 SECOND, INTERVAL 2 MINUTE, INTERVAL -3 MONTH, INTERVAL 1 YEAR); -SELECT '2022-10-11'::Date + INTERVAL '-1 SECOND 2 MINUTE -3 MONTH 1 YEAR'; +WITH INTERVAL 1 SECOND + INTERVAL 1 SECOND + INTERVAL 1 SECOND as expr SELECT expr, toTypeName(expr); +WITH INTERVAL 1 HOUR + INTERVAL 1 SECOND + INTERVAL 1 SECOND as expr SELECT expr, toTypeName(expr); +WITH INTERVAL 1 SECOND + INTERVAL 1 HOUR + INTERVAL 1 SECOND as expr SELECT expr, toTypeName(expr); +WITH INTERVAL 1 SECOND + INTERVAL 1 SECOND + INTERVAL 1 HOUR as expr SELECT expr, toTypeName(expr); -SELECT '-'; -SELECT '2022-10-11'::DateTime - INTERVAL 1 QUARTER - INTERVAL -3 WEEK - INTERVAL 1 YEAR - INTERVAL 1 HOUR; -SELECT '2022-10-11'::DateTime - (INTERVAL 1 QUARTER, INTERVAL -3 WEEK, INTERVAL 1 YEAR, INTERVAL 1 HOUR); -SELECT '2022-10-11'::DateTime - INTERVAL '1 QUARTER -3 WEEK 1 YEAR 1 HOUR'; +SELECT '---'; -SELECT '-'; -SELECT '2022-10-11'::DateTime64 - INTERVAL 1 YEAR - INTERVAL 4 MONTH - INTERVAL 1 SECOND; -SELECT '2022-10-11'::DateTime64 - (INTERVAL 1 YEAR, INTERVAL 4 MONTH, INTERVAL 1 SECOND); -SELECT '2022-10-11'::DateTime64 - INTERVAL '1 YEAR 4 MONTH 1 SECOND'; +WITH - INTERVAL 1 SECOND - INTERVAL 1 SECOND - INTERVAL 1 SECOND as expr SELECT expr, toTypeName(expr); +WITH - INTERVAL 1 HOUR - INTERVAL 1 SECOND - INTERVAL 1 SECOND as expr SELECT expr, toTypeName(expr); +WITH - INTERVAL 1 SECOND - INTERVAL 1 HOUR - INTERVAL 1 SECOND as expr SELECT expr, toTypeName(expr); +WITH - INTERVAL 1 SECOND - INTERVAL 1 SECOND - INTERVAL 1 HOUR as expr SELECT expr, toTypeName(expr); + +SELECT '---'; + +WITH '2022-10-11'::Date + INTERVAL 1 DAY + INTERVAL 1 MONTH AS e1, + '2022-10-11'::Date + (INTERVAL 1 DAY + INTERVAL 1 MONTH) AS e2, + '2022-10-11'::Date + (INTERVAL 1 DAY, INTERVAL 1 MONTH) AS e3, + '2022-10-11'::Date + INTERVAL '1 DAY 1 MONTH' AS e4 +SELECT e1 == e2 AND e2 == e3 AND e3 == e4; + +WITH '2022-10-11'::Date + INTERVAL -1 SECOND + INTERVAL 2 MINUTE + INTERVAL -3 MONTH + INTERVAL 1 YEAR AS e1, + '2022-10-11'::Date + (INTERVAL -1 SECOND + INTERVAL 2 MINUTE + INTERVAL -3 MONTH + INTERVAL 1 YEAR) AS e2, + '2022-10-11'::Date + (INTERVAL -1 SECOND, INTERVAL 2 MINUTE, INTERVAL -3 MONTH, INTERVAL 1 YEAR) AS e3, + '2022-10-11'::Date + INTERVAL '-1 SECOND 2 MINUTE -3 MONTH 1 YEAR' AS e4 +SELECT e1 == e2 AND e2 == e3 AND e3 == e4; + +WITH '2022-10-11'::DateTime - INTERVAL 1 QUARTER - INTERVAL -3 WEEK - INTERVAL 1 YEAR - INTERVAL 1 HOUR AS e1, + '2022-10-11'::DateTime + (- INTERVAL 1 QUARTER - INTERVAL -3 WEEK - INTERVAL 1 YEAR - INTERVAL 1 HOUR) AS e2, + '2022-10-11'::DateTime - (INTERVAL 1 QUARTER, INTERVAL -3 WEEK, INTERVAL 1 YEAR, INTERVAL 1 HOUR) AS e3, + '2022-10-11'::DateTime - INTERVAL '1 QUARTER -3 WEEK 1 YEAR 1 HOUR' AS e4 +SELECT e1 == e2 AND e2 == e3 AND e3 == e4; + + +WITH '2022-10-11'::DateTime64 - INTERVAL 1 YEAR - INTERVAL 4 MONTH - INTERVAL 1 SECOND AS e1, + '2022-10-11'::DateTime64 + (- INTERVAL 1 YEAR - INTERVAL 4 MONTH - INTERVAL 1 SECOND) AS e2, + '2022-10-11'::DateTime64 - (INTERVAL 1 YEAR, INTERVAL 4 MONTH, INTERVAL 1 SECOND) AS e3, + '2022-10-11'::DateTime64 - INTERVAL '1 YEAR 4 MONTH 1 SECOND' AS e4 +SELECT e1 == e2 AND e2 == e3 AND e3 == e4; \ No newline at end of file From cf6471c6b9965a82acb16b4a576e5f8a1ecf123d Mon Sep 17 00:00:00 2001 From: Nikolay Degterinsky Date: Fri, 14 Oct 2022 02:48:04 +0000 Subject: [PATCH 048/266] Fix style --- src/Functions/FunctionBinaryArithmetic.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Functions/FunctionBinaryArithmetic.h b/src/Functions/FunctionBinaryArithmetic.h index e4919d3e9d7..c13cc67a1bc 100644 --- a/src/Functions/FunctionBinaryArithmetic.h +++ b/src/Functions/FunctionBinaryArithmetic.h @@ -749,7 +749,7 @@ class FunctionBinaryArithmetic : public IFunction getFunctionForMergeIntervalsArithmetic(const DataTypePtr & type0, const DataTypePtr & type1, ContextPtr context) { /// Special case when the function is plus or minus, first argument is Interval or Tuple of Intervals - /// and the second argument is the Inteval of a different kind. + /// and the second argument is the Interval of a different kind. /// We construct another function (example: addIntervals) and call it if constexpr (!is_plus && !is_minus) From 8ebe01d9cf1807fcfe782de6efb61142a81e6d5b Mon Sep 17 00:00:00 2001 From: xiedeyantu Date: Fri, 14 Oct 2022 11:33:56 +0800 Subject: [PATCH 049/266] system.detached_parts add column bytes_on_disk and path --- .../MergeTree/DataPartStorageOnDisk.cpp | 5 +++++ .../MergeTree/DataPartStorageOnDisk.h | 4 ++++ .../System/StorageSystemDetachedParts.cpp | 20 ++++++++++++------- 3 files changed, 22 insertions(+), 7 deletions(-) diff --git a/src/Storages/MergeTree/DataPartStorageOnDisk.cpp b/src/Storages/MergeTree/DataPartStorageOnDisk.cpp index e2a2f3f793f..e50cee4b654 100644 --- a/src/Storages/MergeTree/DataPartStorageOnDisk.cpp +++ b/src/Storages/MergeTree/DataPartStorageOnDisk.cpp @@ -121,6 +121,11 @@ static UInt64 calculateTotalSizeOnDiskImpl(const DiskPtr & disk, const String & return res; } +UInt64 DataPartStorageOnDisk::calculateTotalSizeOnDisk(const DiskPtr & disk, const String & from) +{ + return calculateTotalSizeOnDiskImpl(disk, from); +} + UInt64 DataPartStorageOnDisk::calculateTotalSizeOnDisk() const { return calculateTotalSizeOnDiskImpl(volume->getDisk(), fs::path(root_path) / part_dir); diff --git a/src/Storages/MergeTree/DataPartStorageOnDisk.h b/src/Storages/MergeTree/DataPartStorageOnDisk.h index adf1b78cdfb..0ad4e8f5239 100644 --- a/src/Storages/MergeTree/DataPartStorageOnDisk.h +++ b/src/Storages/MergeTree/DataPartStorageOnDisk.h @@ -112,6 +112,9 @@ public: void changeRootPath(const std::string & from_root, const std::string & to_root) override; DataPartStorageBuilderPtr getBuilder() const override; + + static UInt64 calculateTotalSizeOnDisk(const DiskPtr &disk, const String &from); + private: VolumePtr volume; std::string root_path; @@ -127,6 +130,7 @@ private: MergeTreeDataPartState state, Poco::Logger * log, bool is_projection) const; + }; class DataPartStorageBuilderOnDisk final : public IDataPartStorageBuilder diff --git a/src/Storages/System/StorageSystemDetachedParts.cpp b/src/Storages/System/StorageSystemDetachedParts.cpp index 574ce4f44c2..9220a053e04 100644 --- a/src/Storages/System/StorageSystemDetachedParts.cpp +++ b/src/Storages/System/StorageSystemDetachedParts.cpp @@ -4,6 +4,7 @@ #include #include #include +#include #include #include #include @@ -16,15 +17,17 @@ StorageSystemDetachedParts::StorageSystemDetachedParts(const StorageID & table_i { StorageInMemoryMetadata storage_metadata; storage_metadata.setColumns(ColumnsDescription{{ - {"database", std::make_shared()}, - {"table", std::make_shared()}, - {"partition_id", std::make_shared(std::make_shared())}, - {"name", std::make_shared()}, - {"disk", std::make_shared()}, - {"reason", std::make_shared(std::make_shared())}, + {"database", std::make_shared()}, + {"table", std::make_shared()}, + {"partition_id", std::make_shared(std::make_shared())}, + {"name", std::make_shared()}, + {"bytes_on_disk", std::make_shared()}, + {"disk", std::make_shared()}, + {"path", std::make_shared()}, + {"reason", std::make_shared(std::make_shared())}, {"min_block_number", std::make_shared(std::make_shared())}, {"max_block_number", std::make_shared(std::make_shared())}, - {"level", std::make_shared(std::make_shared())} + {"level", std::make_shared(std::make_shared())} }}); setInMemoryMetadata(storage_metadata); } @@ -50,11 +53,14 @@ Pipe StorageSystemDetachedParts::read( for (const auto & p : parts) { size_t i = 0; + String detached_part_path = fs::path(MergeTreeData::DETACHED_DIR_NAME) / p.dir_name; new_columns[i++]->insert(info.database); new_columns[i++]->insert(info.table); new_columns[i++]->insert(p.valid_name ? p.partition_id : Field()); new_columns[i++]->insert(p.dir_name); + new_columns[i++]->insert(DataPartStorageOnDisk::calculateTotalSizeOnDisk(p.disk, fs::path(info.data->getRelativeDataPath()) / detached_part_path)); new_columns[i++]->insert(p.disk->getName()); + new_columns[i++]->insert((fs::path(info.data->getFullPathOnDisk(p.disk)) / detached_part_path).string()); new_columns[i++]->insert(p.valid_name ? p.prefix : Field()); new_columns[i++]->insert(p.valid_name ? p.min_block : Field()); new_columns[i++]->insert(p.valid_name ? p.max_block : Field()); From 555d2759202eaa1d08ba3c54dcc99507e188c069 Mon Sep 17 00:00:00 2001 From: Kseniia Sumarokova <54203879+kssenii@users.noreply.github.com> Date: Fri, 14 Oct 2022 15:18:53 +0200 Subject: [PATCH 050/266] Update src/Storages/MergeTree/DataPartStorageOnDisk.h --- src/Storages/MergeTree/DataPartStorageOnDisk.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Storages/MergeTree/DataPartStorageOnDisk.h b/src/Storages/MergeTree/DataPartStorageOnDisk.h index 0ad4e8f5239..79988e9baab 100644 --- a/src/Storages/MergeTree/DataPartStorageOnDisk.h +++ b/src/Storages/MergeTree/DataPartStorageOnDisk.h @@ -113,7 +113,7 @@ public: DataPartStorageBuilderPtr getBuilder() const override; - static UInt64 calculateTotalSizeOnDisk(const DiskPtr &disk, const String &from); + static UInt64 calculateTotalSizeOnDisk(const DiskPtr & disk, const String & from); private: VolumePtr volume; From c11e4bfbbfbc36999814e5983e19e41b1eecd2ea Mon Sep 17 00:00:00 2001 From: Nikolay Degterinsky Date: Fri, 14 Oct 2022 15:13:26 +0000 Subject: [PATCH 051/266] Fix build --- src/Functions/vectorFunctions.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Functions/vectorFunctions.cpp b/src/Functions/vectorFunctions.cpp index 007875a8b81..78f35342d60 100644 --- a/src/Functions/vectorFunctions.cpp +++ b/src/Functions/vectorFunctions.cpp @@ -555,7 +555,7 @@ public: { const auto & cur_types = tuple->getElements(); - for (auto & type : cur_types) + for (const auto & type : cur_types) if (!isInterval(type)) throw Exception(ErrorCodes::ILLEGAL_TYPE_OF_ARGUMENT, "Illegal type {} of Tuple element of first argument of function {}, must be Interval", @@ -607,7 +607,7 @@ public: { const auto & cur_types = first_tuple->getElements(); - for (auto & type : cur_types) + for (const auto & type : cur_types) if (!isInterval(type)) throw Exception(ErrorCodes::ILLEGAL_TYPE_OF_ARGUMENT, "Illegal type {} of Tuple element of first argument of function {}, must be Interval", From 65a0b9fcc60d0254e5f2d2b020af73c1b03b3b41 Mon Sep 17 00:00:00 2001 From: xiedeyantu Date: Fri, 14 Oct 2022 23:32:13 +0800 Subject: [PATCH 052/266] fix test --- .../00502_custom_partitioning_local.reference | 2 +- .../00502_custom_partitioning_local.sql | 2 +- .../02117_show_create_table_system.reference | 133 +----------------- 3 files changed, 4 insertions(+), 133 deletions(-) diff --git a/tests/queries/0_stateless/00502_custom_partitioning_local.reference b/tests/queries/0_stateless/00502_custom_partitioning_local.reference index fff28819e74..be02d3eeefc 100644 --- a/tests/queries/0_stateless/00502_custom_partitioning_local.reference +++ b/tests/queries/0_stateless/00502_custom_partitioning_local.reference @@ -9,7 +9,7 @@ Sum before DETACH PARTITION: Sum after DETACH PARTITION: 0 system.detached_parts after DETACH PARTITION: -default not_partitioned all all_1_2_1 1 2 1 +default not_partitioned all all_1_2_1 324 default 1 2 1 default *** Partitioned by week *** Parts before OPTIMIZE: 1999-12-27 19991227_1_1_0 diff --git a/tests/queries/0_stateless/00502_custom_partitioning_local.sql b/tests/queries/0_stateless/00502_custom_partitioning_local.sql index c85a978af68..df4785af90d 100644 --- a/tests/queries/0_stateless/00502_custom_partitioning_local.sql +++ b/tests/queries/0_stateless/00502_custom_partitioning_local.sql @@ -18,7 +18,7 @@ ALTER TABLE not_partitioned DETACH PARTITION ID 'all'; SELECT 'Sum after DETACH PARTITION:'; SELECT sum(x) FROM not_partitioned; SELECT 'system.detached_parts after DETACH PARTITION:'; -SELECT system.detached_parts.* EXCEPT disk FROM system.detached_parts WHERE database = currentDatabase() AND table = 'not_partitioned'; +SELECT system.detached_parts.* EXCEPT `path`, disk FROM system.detached_parts WHERE database = currentDatabase() AND table = 'not_partitioned'; DROP TABLE not_partitioned; diff --git a/tests/queries/0_stateless/02117_show_create_table_system.reference b/tests/queries/0_stateless/02117_show_create_table_system.reference index ad27b86c6f5..ee2ccc8b7d1 100644 --- a/tests/queries/0_stateless/02117_show_create_table_system.reference +++ b/tests/queries/0_stateless/02117_show_create_table_system.reference @@ -139,7 +139,9 @@ CREATE TABLE system.detached_parts `table` String, `partition_id` Nullable(String), `name` String, + `bytes_on_disk` UInt64, `disk` String, + `path` String, `reason` Nullable(String), `min_block_number` Nullable(Int64), `max_block_number` Nullable(Int64), @@ -974,134 +976,3 @@ CREATE TABLE system.settings_profiles ) ENGINE = SystemSettingsProfiles COMMENT 'SYSTEM TABLE is built on the fly.' -CREATE TABLE system.stack_trace -( - `thread_name` String, - `thread_id` UInt64, - `query_id` String, - `trace` Array(UInt64) -) -ENGINE = SystemStackTrace -COMMENT 'SYSTEM TABLE is built on the fly.' -CREATE TABLE system.storage_policies -( - `policy_name` String, - `volume_name` String, - `volume_priority` UInt64, - `disks` Array(String), - `volume_type` String, - `max_data_part_size` UInt64, - `move_factor` Float32, - `prefer_not_to_merge` UInt8 -) -ENGINE = SystemStoragePolicies -COMMENT 'SYSTEM TABLE is built on the fly.' -CREATE TABLE system.table_engines -( - `name` String, - `supports_settings` UInt8, - `supports_skipping_indices` UInt8, - `supports_projections` UInt8, - `supports_sort_order` UInt8, - `supports_ttl` UInt8, - `supports_replication` UInt8, - `supports_deduplication` UInt8, - `supports_parallel_insert` UInt8 -) -ENGINE = SystemTableEngines -COMMENT 'SYSTEM TABLE is built on the fly.' -CREATE TABLE system.table_functions -( - `name` String, - `description` String -) -ENGINE = SystemTableFunctions -COMMENT 'SYSTEM TABLE is built on the fly.' -CREATE TABLE system.tables -( - `database` String, - `name` String, - `uuid` UUID, - `engine` String, - `is_temporary` UInt8, - `data_paths` Array(String), - `metadata_path` String, - `metadata_modification_time` DateTime, - `dependencies_database` Array(String), - `dependencies_table` Array(String), - `create_table_query` String, - `engine_full` String, - `as_select` String, - `partition_key` String, - `sorting_key` String, - `primary_key` String, - `sampling_key` String, - `storage_policy` String, - `total_rows` Nullable(UInt64), - `total_bytes` Nullable(UInt64), - `lifetime_rows` Nullable(UInt64), - `lifetime_bytes` Nullable(UInt64), - `comment` String, - `has_own_data` UInt8, - `loading_dependencies_database` Array(String), - `loading_dependencies_table` Array(String), - `loading_dependent_database` Array(String), - `loading_dependent_table` Array(String), - `table` String -) -ENGINE = SystemTables -COMMENT 'SYSTEM TABLE is built on the fly.' -CREATE TABLE system.time_zones -( - `time_zone` String -) -ENGINE = SystemTimeZones -COMMENT 'SYSTEM TABLE is built on the fly.' -CREATE TABLE system.user_directories -( - `name` String, - `type` String, - `params` String, - `precedence` UInt64 -) -ENGINE = SystemUserDirectories -COMMENT 'SYSTEM TABLE is built on the fly.' -CREATE TABLE system.users -( - `name` String, - `id` UUID, - `storage` String, - `auth_type` Enum8('no_password' = 0, 'plaintext_password' = 1, 'sha256_password' = 2, 'double_sha1_password' = 3, 'ldap' = 4, 'kerberos' = 5, 'ssl_certificate' = 6), - `auth_params` String, - `host_ip` Array(String), - `host_names` Array(String), - `host_names_regexp` Array(String), - `host_names_like` Array(String), - `default_roles_all` UInt8, - `default_roles_list` Array(String), - `default_roles_except` Array(String), - `grantees_any` UInt8, - `grantees_list` Array(String), - `grantees_except` Array(String), - `default_database` String -) -ENGINE = SystemUsers -COMMENT 'SYSTEM TABLE is built on the fly.' -CREATE TABLE system.warnings -( - `message` String -) -ENGINE = SystemWarnings -COMMENT 'SYSTEM TABLE is built on the fly.' -CREATE TABLE system.zeros -( - `zero` UInt8 -) -ENGINE = SystemZeros -COMMENT 'SYSTEM TABLE is built on the fly.' -CREATE TABLE system.zeros_mt -( - `zero` UInt8 -) -ENGINE = SystemZeros -COMMENT 'SYSTEM TABLE is built on the fly.' From 66f6c0a68322f182c80fb86ad0ad9fc646a25ae5 Mon Sep 17 00:00:00 2001 From: xiedeyantu Date: Sat, 15 Oct 2022 22:43:55 +0800 Subject: [PATCH 053/266] fix test --- .../02117_show_create_table_system.reference | 131 ++++++++++++++++++ 1 file changed, 131 insertions(+) diff --git a/tests/queries/0_stateless/02117_show_create_table_system.reference b/tests/queries/0_stateless/02117_show_create_table_system.reference index ee2ccc8b7d1..4d705e4b0d1 100644 --- a/tests/queries/0_stateless/02117_show_create_table_system.reference +++ b/tests/queries/0_stateless/02117_show_create_table_system.reference @@ -976,3 +976,134 @@ CREATE TABLE system.settings_profiles ) ENGINE = SystemSettingsProfiles COMMENT 'SYSTEM TABLE is built on the fly.' +CREATE TABLE system.stack_trace +( + `thread_name` String, + `thread_id` UInt64, + `query_id` String, + `trace` Array(UInt64) +) +ENGINE = SystemStackTrace +COMMENT 'SYSTEM TABLE is built on the fly.' +CREATE TABLE system.storage_policies +( + `policy_name` String, + `volume_name` String, + `volume_priority` UInt64, + `disks` Array(String), + `volume_type` String, + `max_data_part_size` UInt64, + `move_factor` Float32, + `prefer_not_to_merge` UInt8 +) +ENGINE = SystemStoragePolicies +COMMENT 'SYSTEM TABLE is built on the fly.' +CREATE TABLE system.table_engines +( + `name` String, + `supports_settings` UInt8, + `supports_skipping_indices` UInt8, + `supports_projections` UInt8, + `supports_sort_order` UInt8, + `supports_ttl` UInt8, + `supports_replication` UInt8, + `supports_deduplication` UInt8, + `supports_parallel_insert` UInt8 +) +ENGINE = SystemTableEngines +COMMENT 'SYSTEM TABLE is built on the fly.' +CREATE TABLE system.table_functions +( + `name` String, + `description` String +) +ENGINE = SystemTableFunctions +COMMENT 'SYSTEM TABLE is built on the fly.' +CREATE TABLE system.tables +( + `database` String, + `name` String, + `uuid` UUID, + `engine` String, + `is_temporary` UInt8, + `data_paths` Array(String), + `metadata_path` String, + `metadata_modification_time` DateTime, + `dependencies_database` Array(String), + `dependencies_table` Array(String), + `create_table_query` String, + `engine_full` String, + `as_select` String, + `partition_key` String, + `sorting_key` String, + `primary_key` String, + `sampling_key` String, + `storage_policy` String, + `total_rows` Nullable(UInt64), + `total_bytes` Nullable(UInt64), + `lifetime_rows` Nullable(UInt64), + `lifetime_bytes` Nullable(UInt64), + `comment` String, + `has_own_data` UInt8, + `loading_dependencies_database` Array(String), + `loading_dependencies_table` Array(String), + `loading_dependent_database` Array(String), + `loading_dependent_table` Array(String), + `table` String +) +ENGINE = SystemTables +COMMENT 'SYSTEM TABLE is built on the fly.' +CREATE TABLE system.time_zones +( + `time_zone` String +) +ENGINE = SystemTimeZones +COMMENT 'SYSTEM TABLE is built on the fly.' +CREATE TABLE system.user_directories +( + `name` String, + `type` String, + `params` String, + `precedence` UInt64 +) +ENGINE = SystemUserDirectories +COMMENT 'SYSTEM TABLE is built on the fly.' +CREATE TABLE system.users +( + `name` String, + `id` UUID, + `storage` String, + `auth_type` Enum8('no_password' = 0, 'plaintext_password' = 1, 'sha256_password' = 2, 'double_sha1_password' = 3, 'ldap' = 4, 'kerberos' = 5, 'ssl_certificate' = 6), + `auth_params` String, + `host_ip` Array(String), + `host_names` Array(String), + `host_names_regexp` Array(String), + `host_names_like` Array(String), + `default_roles_all` UInt8, + `default_roles_list` Array(String), + `default_roles_except` Array(String), + `grantees_any` UInt8, + `grantees_list` Array(String), + `grantees_except` Array(String), + `default_database` String +) +ENGINE = SystemUsers +COMMENT 'SYSTEM TABLE is built on the fly.' +CREATE TABLE system.warnings +( + `message` String +) +ENGINE = SystemWarnings +COMMENT 'SYSTEM TABLE is built on the fly.' +CREATE TABLE system.zeros +( + `zero` UInt8 +) +ENGINE = SystemZeros +COMMENT 'SYSTEM TABLE is built on the fly.' +CREATE TABLE system.zeros_mt +( + `zero` UInt8 +) +ENGINE = SystemZeros +COMMENT 'SYSTEM TABLE is built on the fly.' From 2a356715da9a44f9273daab8fc3e6e71c9a52d08 Mon Sep 17 00:00:00 2001 From: xiedeyantu Date: Sun, 16 Oct 2022 16:46:00 +0800 Subject: [PATCH 054/266] fix test --- src/Storages/MergeTree/DataPartStorageOnDisk.cpp | 13 ++++++++++++- tests/integration/test_partition/test.py | 2 +- 2 files changed, 13 insertions(+), 2 deletions(-) diff --git a/src/Storages/MergeTree/DataPartStorageOnDisk.cpp b/src/Storages/MergeTree/DataPartStorageOnDisk.cpp index e50cee4b654..8ceebae333c 100644 --- a/src/Storages/MergeTree/DataPartStorageOnDisk.cpp +++ b/src/Storages/MergeTree/DataPartStorageOnDisk.cpp @@ -112,7 +112,18 @@ UInt32 DataPartStorageOnDisk::getRefCount(const String & file_name) const static UInt64 calculateTotalSizeOnDiskImpl(const DiskPtr & disk, const String & from) { if (disk->isFile(from)) - return disk->getFileSize(from); + { + try + { + return disk->getFileSize(from); + } + catch (...) + { + /// Files of detached part may be not exist, and then set file size is 0. + return 0; + } + } + std::vector files; disk->listFiles(from, files); UInt64 res = 0; diff --git a/tests/integration/test_partition/test.py b/tests/integration/test_partition/test.py index f3df66631a5..18e59becdd4 100644 --- a/tests/integration/test_partition/test.py +++ b/tests/integration/test_partition/test.py @@ -379,7 +379,7 @@ def test_system_detached_parts(drop_detached_parts_table): ) res = q( - "select * from system.detached_parts where table like 'sdp_%' order by table, name" + "select system.detached_parts.* except `path`, disk from system.detached_parts where table like 'sdp_%' order by table, name" ) assert ( res == "default\tsdp_0\tall\tall_1_1_0\tdefault\t\t1\t1\t0\n" From 9ec206f8f74a6e6fb94a581dee30dc87b7bcd7d8 Mon Sep 17 00:00:00 2001 From: xiedeyantu Date: Sun, 16 Oct 2022 19:52:53 +0800 Subject: [PATCH 055/266] fix test --- .../MergeTree/DataPartStorageOnDisk.cpp | 25 +++++++++---------- .../00502_custom_partitioning_local.reference | 2 +- .../00502_custom_partitioning_local.sql | 2 +- 3 files changed, 14 insertions(+), 15 deletions(-) diff --git a/src/Storages/MergeTree/DataPartStorageOnDisk.cpp b/src/Storages/MergeTree/DataPartStorageOnDisk.cpp index 8ceebae333c..600544b0b20 100644 --- a/src/Storages/MergeTree/DataPartStorageOnDisk.cpp +++ b/src/Storages/MergeTree/DataPartStorageOnDisk.cpp @@ -112,18 +112,7 @@ UInt32 DataPartStorageOnDisk::getRefCount(const String & file_name) const static UInt64 calculateTotalSizeOnDiskImpl(const DiskPtr & disk, const String & from) { if (disk->isFile(from)) - { - try - { - return disk->getFileSize(from); - } - catch (...) - { - /// Files of detached part may be not exist, and then set file size is 0. - return 0; - } - } - + return disk->getFileSize(from); std::vector files; disk->listFiles(from, files); UInt64 res = 0; @@ -134,7 +123,17 @@ static UInt64 calculateTotalSizeOnDiskImpl(const DiskPtr & disk, const String & UInt64 DataPartStorageOnDisk::calculateTotalSizeOnDisk(const DiskPtr & disk, const String & from) { - return calculateTotalSizeOnDiskImpl(disk, from); + try + { + /// Files of detached part may be not exist, and then set file size is 0. + if (!disk->exists(from)) + return 0; + return calculateTotalSizeOnDiskImpl(disk, from); + } + catch (...) + { + return 0; + } } UInt64 DataPartStorageOnDisk::calculateTotalSizeOnDisk() const diff --git a/tests/queries/0_stateless/00502_custom_partitioning_local.reference b/tests/queries/0_stateless/00502_custom_partitioning_local.reference index be02d3eeefc..226a39f628e 100644 --- a/tests/queries/0_stateless/00502_custom_partitioning_local.reference +++ b/tests/queries/0_stateless/00502_custom_partitioning_local.reference @@ -9,7 +9,7 @@ Sum before DETACH PARTITION: Sum after DETACH PARTITION: 0 system.detached_parts after DETACH PARTITION: -default not_partitioned all all_1_2_1 324 default 1 2 1 default +default not_partitioned all all_1_2_1 324 1 2 1 *** Partitioned by week *** Parts before OPTIMIZE: 1999-12-27 19991227_1_1_0 diff --git a/tests/queries/0_stateless/00502_custom_partitioning_local.sql b/tests/queries/0_stateless/00502_custom_partitioning_local.sql index df4785af90d..87a6331402b 100644 --- a/tests/queries/0_stateless/00502_custom_partitioning_local.sql +++ b/tests/queries/0_stateless/00502_custom_partitioning_local.sql @@ -18,7 +18,7 @@ ALTER TABLE not_partitioned DETACH PARTITION ID 'all'; SELECT 'Sum after DETACH PARTITION:'; SELECT sum(x) FROM not_partitioned; SELECT 'system.detached_parts after DETACH PARTITION:'; -SELECT system.detached_parts.* EXCEPT `path`, disk FROM system.detached_parts WHERE database = currentDatabase() AND table = 'not_partitioned'; +SELECT system.detached_parts.* EXCEPT (`path`, disk) FROM system.detached_parts WHERE database = currentDatabase() AND table = 'not_partitioned'; DROP TABLE not_partitioned; From 9320ae1abf34ec596161b41f1852abf9deb240e4 Mon Sep 17 00:00:00 2001 From: Yatsishin Ilya <2159081+qoega@users.noreply.github.com> Date: Mon, 17 Oct 2022 13:10:31 +0000 Subject: [PATCH 056/266] update sqlancer docker files --- docker/test/sqlancer/Dockerfile | 2 +- docker/test/sqlancer/run.sh | 16 ++++++++-------- 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/docker/test/sqlancer/Dockerfile b/docker/test/sqlancer/Dockerfile index 0821d516e23..2ebc61e35a9 100644 --- a/docker/test/sqlancer/Dockerfile +++ b/docker/test/sqlancer/Dockerfile @@ -1,5 +1,5 @@ # docker build -t clickhouse/sqlancer-test . -FROM ubuntu:20.04 +FROM ubuntu:22.04 # ARG for quick switch to a given ubuntu mirror ARG apt_archive="http://archive.ubuntu.com" diff --git a/docker/test/sqlancer/run.sh b/docker/test/sqlancer/run.sh index a1891569d34..3f08568159b 100755 --- a/docker/test/sqlancer/run.sh +++ b/docker/test/sqlancer/run.sh @@ -1,13 +1,10 @@ #!/bin/bash +set -exu +trap "exit" INT TERM -set -e -x +/clickhouse server -P /clickhouse-server.pid -L /clickhouse-server.log -E /clickhouse-server.log.err --daemon -dpkg -i package_folder/clickhouse-common-static_*.deb -dpkg -i package_folder/clickhouse-common-static-dbg_*.deb -dpkg -i package_folder/clickhouse-server_*.deb -dpkg -i package_folder/clickhouse-client_*.deb - -service clickhouse-server start && sleep 5 +for i in `seq 1 30`; do if [[ `wget -q 'localhost:8123' -O-` == 'Ok.' ]]; then break ; else sleep 1; fi ; done cd /sqlancer/sqlancer-master @@ -21,7 +18,10 @@ export NUM_QUERIES=1000 ( java -jar target/sqlancer-*.jar --num-threads 10 --timeout-seconds $TIMEOUT --num-queries $NUM_QUERIES --username default --password "" clickhouse --oracle TLPDistinct | tee /test_output/TLPDistinct.out ) 3>&1 1>&2 2>&3 | tee /test_output/TLPDistinct.err ( java -jar target/sqlancer-*.jar --num-threads 10 --timeout-seconds $TIMEOUT --num-queries $NUM_QUERIES --username default --password "" clickhouse --oracle TLPAggregate | tee /test_output/TLPAggregate.out ) 3>&1 1>&2 2>&3 | tee /test_output/TLPAggregate.err -service clickhouse stop +pkill -F ./clickhouse-server.pid + +for i in `seq 1 30`; do if [[ `wget -q 'localhost:8123' -O-` == 'Ok.' ]]; then sleep 1 ; else break; fi ; done + ls /var/log/clickhouse-server/ tar czf /test_output/logs.tar.gz -C /var/log/clickhouse-server/ . From 6a82d54314721844d4289201dc9bfc60be4c6a86 Mon Sep 17 00:00:00 2001 From: Yatsishin Ilya <2159081+qoega@users.noreply.github.com> Date: Mon, 17 Oct 2022 14:46:50 +0000 Subject: [PATCH 057/266] add CI files --- .github/workflows/master.yml | 38 ++++++ .github/workflows/pull_request.yml | 39 +++++- docker/test/sqlancer/run.sh | 42 +++++-- tests/ci/ci_config.py | 3 + tests/ci/sqlancer_check.py | 183 +++++++++++++++++++++++++++++ 5 files changed, 296 insertions(+), 9 deletions(-) create mode 100644 tests/ci/sqlancer_check.py diff --git a/.github/workflows/master.yml b/.github/workflows/master.yml index 3d22cb984dd..b373a3cc210 100644 --- a/.github/workflows/master.yml +++ b/.github/workflows/master.yml @@ -2992,6 +2992,43 @@ jobs: docker ps --quiet | xargs --no-run-if-empty docker kill ||: docker ps --all --quiet | xargs --no-run-if-empty docker rm -f ||: sudo rm -fr "$TEMP_PATH" +############################################################################################## +###################################### SQLANCER FUZZERS ###################################### +############################################################################################## + SQLancerTestRelease: + needs: [BuilderDebRelease] + runs-on: [self-hosted, fuzzer-unit-tester] + steps: + - name: Set envs + run: | + cat >> "$GITHUB_ENV" << 'EOF' + TEMP_PATH=${{runner.temp}}/sqlancer_release + REPORTS_PATH=${{runner.temp}}/reports_dir + CHECK_NAME=SQLancer (release) + REPO_COPY=${{runner.temp}}/sqlancer_release/ClickHouse + EOF + - name: Download json reports + uses: actions/download-artifact@v2 + with: + path: ${{ env.REPORTS_PATH }} + - name: Clear repository + run: | + sudo rm -fr "$GITHUB_WORKSPACE" && mkdir "$GITHUB_WORKSPACE" + - name: Check out repository code + uses: actions/checkout@v2 + - name: SQLancer + run: | + sudo rm -fr "$TEMP_PATH" + mkdir -p "$TEMP_PATH" + cp -r "$GITHUB_WORKSPACE" "$TEMP_PATH" + cd "$REPO_COPY/tests/ci" + python3 sqlancer_check.py "$CHECK_NAME" + - name: Cleanup + if: always() + run: | + docker ps --quiet | xargs --no-run-if-empty docker kill ||: + docker ps --all --quiet | xargs --no-run-if-empty docker rm -f ||: + sudo rm -fr "$TEMP_PATH" FinishCheck: needs: - DockerHubPush @@ -3051,6 +3088,7 @@ jobs: - UnitTestsUBsan - UnitTestsReleaseClang - SharedBuildSmokeTest + - SQLancerTestRelease runs-on: [self-hosted, style-checker] steps: - name: Clear repository diff --git a/.github/workflows/pull_request.yml b/.github/workflows/pull_request.yml index 2795dc62d6d..5b1011019f6 100644 --- a/.github/workflows/pull_request.yml +++ b/.github/workflows/pull_request.yml @@ -3500,7 +3500,43 @@ jobs: if: contains(github.event.pull_request.labels.*.name, 'jepsen-test') needs: [BuilderBinRelease] uses: ./.github/workflows/jepsen.yml - +############################################################################################## +###################################### SQLANCER FUZZERS ###################################### +############################################################################################## + SQLancerTestRelease: + needs: [BuilderDebRelease] + runs-on: [self-hosted, fuzzer-unit-tester] + steps: + - name: Set envs + run: | + cat >> "$GITHUB_ENV" << 'EOF' + TEMP_PATH=${{runner.temp}}/sqlancer_release + REPORTS_PATH=${{runner.temp}}/reports_dir + CHECK_NAME=SQLancer (release) + REPO_COPY=${{runner.temp}}/sqlancer_release/ClickHouse + EOF + - name: Download json reports + uses: actions/download-artifact@v2 + with: + path: ${{ env.REPORTS_PATH }} + - name: Clear repository + run: | + sudo rm -fr "$GITHUB_WORKSPACE" && mkdir "$GITHUB_WORKSPACE" + - name: Check out repository code + uses: actions/checkout@v2 + - name: SQLancer + run: | + sudo rm -fr "$TEMP_PATH" + mkdir -p "$TEMP_PATH" + cp -r "$GITHUB_WORKSPACE" "$TEMP_PATH" + cd "$REPO_COPY/tests/ci" + python3 sqlancer_check.py "$CHECK_NAME" + - name: Cleanup + if: always() + run: | + docker ps --quiet | xargs --no-run-if-empty docker kill ||: + docker ps --all --quiet | xargs --no-run-if-empty docker rm -f ||: + sudo rm -fr "$TEMP_PATH" FinishCheck: needs: - StyleCheck @@ -3575,6 +3611,7 @@ jobs: - SharedBuildSmokeTest - CompatibilityCheck - IntegrationTestsFlakyCheck + - SQLancerTestRelease runs-on: [self-hosted, style-checker] steps: - name: Clear repository diff --git a/docker/test/sqlancer/run.sh b/docker/test/sqlancer/run.sh index 3f08568159b..7e35b6073ae 100755 --- a/docker/test/sqlancer/run.sh +++ b/docker/test/sqlancer/run.sh @@ -2,6 +2,33 @@ set -exu trap "exit" INT TERM +function wget_with_retry +{ + for _ in 1 2 3 4; do + if wget -nv -nd -c "$1";then + return 0 + else + sleep 0.5 + fi + done + return 1 +} + +if [ -z ${BINARY_URL_TO_DOWNLOAD+x} ] +then + echo "No BINARY_URL_TO_DOWNLOAD provided." +else + wget_with_retry "$BINARY_URL_TO_DOWNLOAD" + chmod +x /clickhouse +fi + +if [[ -f "/clickhouse" ]]; then + echo "/clickhouse exists" +else + exit 1 +fi + + /clickhouse server -P /clickhouse-server.pid -L /clickhouse-server.log -E /clickhouse-server.log.err --daemon for i in `seq 1 30`; do if [[ `wget -q 'localhost:8123' -O-` == 'Ok.' ]]; then break ; else sleep 1; fi ; done @@ -18,16 +45,15 @@ export NUM_QUERIES=1000 ( java -jar target/sqlancer-*.jar --num-threads 10 --timeout-seconds $TIMEOUT --num-queries $NUM_QUERIES --username default --password "" clickhouse --oracle TLPDistinct | tee /test_output/TLPDistinct.out ) 3>&1 1>&2 2>&3 | tee /test_output/TLPDistinct.err ( java -jar target/sqlancer-*.jar --num-threads 10 --timeout-seconds $TIMEOUT --num-queries $NUM_QUERIES --username default --password "" clickhouse --oracle TLPAggregate | tee /test_output/TLPAggregate.out ) 3>&1 1>&2 2>&3 | tee /test_output/TLPAggregate.err -pkill -F ./clickhouse-server.pid +pkill -F /clickhouse-server.pid for i in `seq 1 30`; do if [[ `wget -q 'localhost:8123' -O-` == 'Ok.' ]]; then sleep 1 ; else break; fi ; done - ls /var/log/clickhouse-server/ -tar czf /test_output/logs.tar.gz -C /var/log/clickhouse-server/ . -tail -n 1000 /var/log/clickhouse-server/stderr.log > /test_output/stderr.log -tail -n 1000 /var/log/clickhouse-server/stdout.log > /test_output/stdout.log -tail -n 1000 /var/log/clickhouse-server/clickhouse-server.log > /test_output/clickhouse-server.log +tar czf /workplace/logs.tar.gz -C /var/log/clickhouse-server/ . +tail -n 1000 /var/log/clickhouse-server/stderr.log > /workplace/stderr.log +tail -n 1000 /var/log/clickhouse-server/stdout.log > /workplace/stdout.log +tail -n 1000 /var/log/clickhouse-server/clickhouse-server.log > /workplace/clickhouse-server.log -/process_sqlancer_result.py || echo -e "failure\tCannot parse results" > /test_output/check_status.tsv -ls /test_output +/process_sqlancer_result.py || echo -e "failure\tCannot parse results" > /workplace/check_status.tsv +ls /workplace diff --git a/tests/ci/ci_config.py b/tests/ci/ci_config.py index 5e69046915e..4b49a088a37 100644 --- a/tests/ci/ci_config.py +++ b/tests/ci/ci_config.py @@ -355,6 +355,9 @@ CI_CONFIG = { "required_build": "package_aarch64", "test_grep_exclude_filter": "", }, + "SQLancer (debug)": { + "required_build": "package_release", + }, }, } # type: dict diff --git a/tests/ci/sqlancer_check.py b/tests/ci/sqlancer_check.py new file mode 100644 index 00000000000..5e6a8629a45 --- /dev/null +++ b/tests/ci/sqlancer_check.py @@ -0,0 +1,183 @@ +#!/usr/bin/env python3 + +import logging +import subprocess +import os +import sys + +from github import Github + +from env_helper import ( + GITHUB_REPOSITORY, + GITHUB_RUN_URL, + REPORTS_PATH, + REPO_COPY, + TEMP_PATH, +) +from s3_helper import S3Helper +from get_robot_token import get_best_robot_token +from pr_info import PRInfo +from build_download_helper import get_build_name_for_check, read_build_urls +from docker_pull_helper import get_image_with_version +from commit_status_helper import post_commit_status +from clickhouse_helper import ClickHouseHelper, prepare_tests_results_for_clickhouse +from stopwatch import Stopwatch +from rerun_helper import RerunHelper + +IMAGE_NAME = "clickhouse/sqlancer-test" + + +def get_run_command(download_url, workspace_path, image): + return ( + f"docker run " + # For sysctl + "--privileged " + "--network=host " + f"--volume={workspace_path}:/workspace " + "--cap-add syslog --cap-add sys_admin --cap-add=SYS_PTRACE " + f'-e BINARY_URL_TO_DOWNLOAD="{download_url}" ' + f"{image}" + ) + + +def get_commit(gh, commit_sha): + repo = gh.get_repo(GITHUB_REPOSITORY) + commit = repo.get_commit(commit_sha) + return commit + + +if __name__ == "__main__": + logging.basicConfig(level=logging.INFO) + + stopwatch = Stopwatch() + + temp_path = TEMP_PATH + repo_path = REPO_COPY + reports_path = REPORTS_PATH + + check_name = sys.argv[1] + + if not os.path.exists(temp_path): + os.makedirs(temp_path) + + pr_info = PRInfo() + + gh = Github(get_best_robot_token(), per_page=100) + + rerun_helper = RerunHelper(gh, pr_info, check_name) + if rerun_helper.is_already_finished_by_status(): + logging.info("Check is already finished according to github status, exiting") + sys.exit(0) + + docker_image = get_image_with_version(temp_path, IMAGE_NAME) + + build_name = get_build_name_for_check(check_name) + print(build_name) + urls = read_build_urls(build_name, reports_path) + if not urls: + raise Exception("No build URLs found") + + for url in urls: + if url.endswith("/clickhouse"): + build_url = url + break + else: + raise Exception("Cannot find binary clickhouse among build results") + + logging.info("Got build url %s", build_url) + + workspace_path = os.path.join(temp_path, "workspace") + if not os.path.exists(workspace_path): + os.makedirs(workspace_path) + + run_command = get_run_command(build_url, workspace_path, docker_image) + logging.info("Going to run %s", run_command) + + run_log_path = os.path.join(temp_path, "runlog.log") + with open(run_log_path, "w", encoding="utf-8") as log: + with subprocess.Popen( + run_command, shell=True, stderr=log, stdout=log + ) as process: + retcode = process.wait() + if retcode == 0: + logging.info("Run successfully") + else: + logging.info("Run failed") + + subprocess.check_call(f"sudo chown -R ubuntu:ubuntu {temp_path}", shell=True) + + check_name_lower = ( + check_name.lower().replace("(", "").replace(")", "").replace(" ", "") + ) + s3_prefix = f"{pr_info.number}/{pr_info.sha}/{check_name_lower}/" + paths = { + "runlog.log": run_log_path, + "TLPWhere": os.path.join(workspace_path, "TLPWhere.log"), + "TLPGroupBy": os.path.join(workspace_path, "TLPGroupBy.log"), + "TLPHaving": os.path.join(workspace_path, "TLPHaving.log"), + "TLPWhereGroupBy": os.path.join(workspace_path, "TLPWhereGroupBy.log"), + "TLPDistinct": os.path.join(workspace_path, "TLPDistinct.log"), + "TLPAggregate": os.path.join(workspace_path, "TLPAggregate.log"), + "stderr.log": os.path.join(workspace_path, "stderr.log"), + "stdout.log": os.path.join(workspace_path, "stdout.log"), + "clickhouse-server.log": os.path.join(workspace_path, "clickhouse-server.log"), + "check_status.tsv": os.path.join(workspace_path, "check_status.tsv"), + "report.html": os.path.join(workspace_path, "report.html"), + "logs.tar.gz": os.path.join(workspace_path, "logs.tar.gz"), + } + + s3_helper = S3Helper() + for f in paths: + try: + paths[f] = s3_helper.upload_test_report_to_s3(paths[f], s3_prefix + "/" + f) + except Exception as ex: + logging.info("Exception uploading file %s text %s", f, ex) + paths[f] = "" + + report_url = GITHUB_RUN_URL + if paths["stderr.log"]: + report_url = paths["stderr.log"] + if paths["stdout.log"]: + report_url = paths["stdout.log"] + if paths["clickhouse-server.log"]: + report_url = paths["clickhouse-server.log"] + if paths["report.html"]: + report_url = paths["report.html"] + + # # Try to get status message saved by the SQLancer + # try: + # with open( + # os.path.join(workspace_path, "status.txt"), "r", encoding="utf-8" + # ) as status_f: + # status = status_f.readline().rstrip("\n") + + # with open( + # os.path.join(workspace_path, "description.txt"), "r", encoding="utf-8" + # ) as desc_f: + # description = desc_f.readline().rstrip("\n")[:140] + # except: + # status = "failure" + # description = "Task failed: $?=" + str(retcode) + + # if "fail" in status: + # test_result = [(description, "FAIL")] + # else: + test_result = [(check_name, "OK")] + + ch_helper = ClickHouseHelper() + + prepared_events = prepare_tests_results_for_clickhouse( + pr_info, + test_result, + status, + stopwatch.duration_seconds, + stopwatch.start_time_str, + report_url, + check_name, + ) + + ch_helper.insert_events_into(db="default", table="checks", events=prepared_events) + + logging.info("Result: '%s', '%s', '%s'", status, description, report_url) + print(f"::notice ::Report url: {report_url}") + post_commit_status(gh, pr_info.sha, check_name, description, status, report_url) From 9e62570d61a3ece8e32468a2479b0ac047051602 Mon Sep 17 00:00:00 2001 From: xiedeyantu Date: Mon, 17 Oct 2022 22:57:11 +0800 Subject: [PATCH 058/266] fix test --- src/Storages/MergeTree/DataPartStorageOnDisk.cpp | 12 +----------- src/Storages/System/StorageSystemDetachedParts.cpp | 13 ++++++++++++- .../00502_custom_partitioning_local.reference | 2 +- .../0_stateless/00502_custom_partitioning_local.sql | 2 +- 4 files changed, 15 insertions(+), 14 deletions(-) diff --git a/src/Storages/MergeTree/DataPartStorageOnDisk.cpp b/src/Storages/MergeTree/DataPartStorageOnDisk.cpp index 600544b0b20..e50cee4b654 100644 --- a/src/Storages/MergeTree/DataPartStorageOnDisk.cpp +++ b/src/Storages/MergeTree/DataPartStorageOnDisk.cpp @@ -123,17 +123,7 @@ static UInt64 calculateTotalSizeOnDiskImpl(const DiskPtr & disk, const String & UInt64 DataPartStorageOnDisk::calculateTotalSizeOnDisk(const DiskPtr & disk, const String & from) { - try - { - /// Files of detached part may be not exist, and then set file size is 0. - if (!disk->exists(from)) - return 0; - return calculateTotalSizeOnDiskImpl(disk, from); - } - catch (...) - { - return 0; - } + return calculateTotalSizeOnDiskImpl(disk, from); } UInt64 DataPartStorageOnDisk::calculateTotalSizeOnDisk() const diff --git a/src/Storages/System/StorageSystemDetachedParts.cpp b/src/Storages/System/StorageSystemDetachedParts.cpp index 9220a053e04..1bc55c922c1 100644 --- a/src/Storages/System/StorageSystemDetachedParts.cpp +++ b/src/Storages/System/StorageSystemDetachedParts.cpp @@ -58,7 +58,18 @@ Pipe StorageSystemDetachedParts::read( new_columns[i++]->insert(info.table); new_columns[i++]->insert(p.valid_name ? p.partition_id : Field()); new_columns[i++]->insert(p.dir_name); - new_columns[i++]->insert(DataPartStorageOnDisk::calculateTotalSizeOnDisk(p.disk, fs::path(info.data->getRelativeDataPath()) / detached_part_path)); + + size_t bytes_on_disk = 0; + try + { + /// Files of detached part may be not exist, and then set file size is 0. + bytes_on_disk = DataPartStorageOnDisk::calculateTotalSizeOnDisk( + p.disk, fs::path(info.data->getRelativeDataPath()) / detached_part_path); + } + catch (...) + {} + + new_columns[i++]->insert(bytes_on_disk); new_columns[i++]->insert(p.disk->getName()); new_columns[i++]->insert((fs::path(info.data->getFullPathOnDisk(p.disk)) / detached_part_path).string()); new_columns[i++]->insert(p.valid_name ? p.prefix : Field()); diff --git a/tests/queries/0_stateless/00502_custom_partitioning_local.reference b/tests/queries/0_stateless/00502_custom_partitioning_local.reference index 226a39f628e..fff28819e74 100644 --- a/tests/queries/0_stateless/00502_custom_partitioning_local.reference +++ b/tests/queries/0_stateless/00502_custom_partitioning_local.reference @@ -9,7 +9,7 @@ Sum before DETACH PARTITION: Sum after DETACH PARTITION: 0 system.detached_parts after DETACH PARTITION: -default not_partitioned all all_1_2_1 324 1 2 1 +default not_partitioned all all_1_2_1 1 2 1 *** Partitioned by week *** Parts before OPTIMIZE: 1999-12-27 19991227_1_1_0 diff --git a/tests/queries/0_stateless/00502_custom_partitioning_local.sql b/tests/queries/0_stateless/00502_custom_partitioning_local.sql index 87a6331402b..3d5f71429fe 100644 --- a/tests/queries/0_stateless/00502_custom_partitioning_local.sql +++ b/tests/queries/0_stateless/00502_custom_partitioning_local.sql @@ -18,7 +18,7 @@ ALTER TABLE not_partitioned DETACH PARTITION ID 'all'; SELECT 'Sum after DETACH PARTITION:'; SELECT sum(x) FROM not_partitioned; SELECT 'system.detached_parts after DETACH PARTITION:'; -SELECT system.detached_parts.* EXCEPT (`path`, disk) FROM system.detached_parts WHERE database = currentDatabase() AND table = 'not_partitioned'; +SELECT system.detached_parts.* EXCEPT (bytes_on_disk, `path`, disk) FROM system.detached_parts WHERE database = currentDatabase() AND table = 'not_partitioned'; DROP TABLE not_partitioned; From b26361c6476ec8ae4081da877b8d5b3f54c4f6e3 Mon Sep 17 00:00:00 2001 From: xiedeyantu Date: Mon, 17 Oct 2022 23:02:39 +0800 Subject: [PATCH 059/266] fix test --- src/Storages/System/StorageSystemDetachedParts.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Storages/System/StorageSystemDetachedParts.cpp b/src/Storages/System/StorageSystemDetachedParts.cpp index 1bc55c922c1..53526a02459 100644 --- a/src/Storages/System/StorageSystemDetachedParts.cpp +++ b/src/Storages/System/StorageSystemDetachedParts.cpp @@ -59,7 +59,7 @@ Pipe StorageSystemDetachedParts::read( new_columns[i++]->insert(p.valid_name ? p.partition_id : Field()); new_columns[i++]->insert(p.dir_name); - size_t bytes_on_disk = 0; + UInt64 bytes_on_disk = 0; try { /// Files of detached part may be not exist, and then set file size is 0. From aa432fe95a9e204dda836c5449223e196c784cb4 Mon Sep 17 00:00:00 2001 From: Yatsishin Ilya <2159081+qoega@users.noreply.github.com> Date: Mon, 17 Oct 2022 15:20:20 +0000 Subject: [PATCH 060/266] staging --- .github/workflows/pull_request.yml | 20 ++++++++++---------- docker/test/sqlancer/run.sh | 4 ++-- tests/ci/sqlancer_check.py | 2 ++ 3 files changed, 14 insertions(+), 12 deletions(-) diff --git a/.github/workflows/pull_request.yml b/.github/workflows/pull_request.yml index 5b1011019f6..6335ccc9a35 100644 --- a/.github/workflows/pull_request.yml +++ b/.github/workflows/pull_request.yml @@ -3490,16 +3490,6 @@ jobs: docker ps --quiet | xargs --no-run-if-empty docker kill ||: docker ps --all --quiet | xargs --no-run-if-empty docker rm -f ||: sudo rm -fr "$TEMP_PATH" -############################################################################################# -###################################### JEPSEN TESTS ######################################### -############################################################################################# - Jepsen: - # This is special test NOT INCLUDED in FinishCheck - # When it's skipped, all dependent tasks will be skipped too. - # DO NOT add it there - if: contains(github.event.pull_request.labels.*.name, 'jepsen-test') - needs: [BuilderBinRelease] - uses: ./.github/workflows/jepsen.yml ############################################################################################## ###################################### SQLANCER FUZZERS ###################################### ############################################################################################## @@ -3537,6 +3527,16 @@ jobs: docker ps --quiet | xargs --no-run-if-empty docker kill ||: docker ps --all --quiet | xargs --no-run-if-empty docker rm -f ||: sudo rm -fr "$TEMP_PATH" +############################################################################################# +###################################### JEPSEN TESTS ######################################### +############################################################################################# + Jepsen: + # This is special test NOT INCLUDED in FinishCheck + # When it's skipped, all dependent tasks will be skipped too. + # DO NOT add it there + if: contains(github.event.pull_request.labels.*.name, 'jepsen-test') + needs: [BuilderBinRelease] + uses: ./.github/workflows/jepsen.yml FinishCheck: needs: - StyleCheck diff --git a/docker/test/sqlancer/run.sh b/docker/test/sqlancer/run.sh index 7e35b6073ae..3a8c43cdfd4 100755 --- a/docker/test/sqlancer/run.sh +++ b/docker/test/sqlancer/run.sh @@ -31,7 +31,7 @@ fi /clickhouse server -P /clickhouse-server.pid -L /clickhouse-server.log -E /clickhouse-server.log.err --daemon -for i in `seq 1 30`; do if [[ `wget -q 'localhost:8123' -O-` == 'Ok.' ]]; then break ; else sleep 1; fi ; done +for i in $(seq 1 30); do if [[ $(wget -q 'localhost:8123' -O-) == 'Ok.' ]]; then break ; else sleep 1; fi ; done cd /sqlancer/sqlancer-master @@ -47,7 +47,7 @@ export NUM_QUERIES=1000 pkill -F /clickhouse-server.pid -for i in `seq 1 30`; do if [[ `wget -q 'localhost:8123' -O-` == 'Ok.' ]]; then sleep 1 ; else break; fi ; done +for i in $(seq 1 30); do if [[ $(wget -q 'localhost:8123' -O-) == 'Ok.' ]]; then sleep 1 ; else break; fi ; done ls /var/log/clickhouse-server/ tar czf /workplace/logs.tar.gz -C /var/log/clickhouse-server/ . diff --git a/tests/ci/sqlancer_check.py b/tests/ci/sqlancer_check.py index 5e6a8629a45..55660e4877b 100644 --- a/tests/ci/sqlancer_check.py +++ b/tests/ci/sqlancer_check.py @@ -162,6 +162,8 @@ if __name__ == "__main__": # if "fail" in status: # test_result = [(description, "FAIL")] # else: + status = "success" + description = "SQLancer runs in always green mode for now" test_result = [(check_name, "OK")] ch_helper = ClickHouseHelper() From 7a74792c1b48dc7a3c3843e79335c815df053dc9 Mon Sep 17 00:00:00 2001 From: Yatsishin Ilya <2159081+qoega@users.noreply.github.com> Date: Mon, 17 Oct 2022 16:30:36 +0000 Subject: [PATCH 061/266] shellcheck --- docker/test/sqlancer/process_sqlancer_result.py | 6 +++--- docker/test/sqlancer/run.sh | 15 ++++++++------- 2 files changed, 11 insertions(+), 10 deletions(-) diff --git a/docker/test/sqlancer/process_sqlancer_result.py b/docker/test/sqlancer/process_sqlancer_result.py index 37b8f465498..5721349b6e5 100755 --- a/docker/test/sqlancer/process_sqlancer_result.py +++ b/docker/test/sqlancer/process_sqlancer_result.py @@ -72,9 +72,9 @@ if __name__ == "__main__": parser = argparse.ArgumentParser( description="ClickHouse script for parsing results of sqlancer test" ) - parser.add_argument("--in-results-dir", default="/test_output/") - parser.add_argument("--out-results-file", default="/test_output/test_results.tsv") - parser.add_argument("--out-status-file", default="/test_output/check_status.tsv") + parser.add_argument("--in-results-dir", default="/workspace/") + parser.add_argument("--out-results-file", default="/workspace/test_results.tsv") + parser.add_argument("--out-status-file", default="/workspace/check_status.tsv") args = parser.parse_args() state, description, test_results, logs = process_result(args.in_results_dir) diff --git a/docker/test/sqlancer/run.sh b/docker/test/sqlancer/run.sh index 3a8c43cdfd4..6a479db8fdd 100755 --- a/docker/test/sqlancer/run.sh +++ b/docker/test/sqlancer/run.sh @@ -31,19 +31,19 @@ fi /clickhouse server -P /clickhouse-server.pid -L /clickhouse-server.log -E /clickhouse-server.log.err --daemon -for i in $(seq 1 30); do if [[ $(wget -q 'localhost:8123' -O-) == 'Ok.' ]]; then break ; else sleep 1; fi ; done +for _ in $(seq 1 30); do if [[ $(wget -q 'localhost:8123' -O-) == 'Ok.' ]]; then break ; else sleep 1; fi ; done cd /sqlancer/sqlancer-master export TIMEOUT=300 export NUM_QUERIES=1000 -( java -jar target/sqlancer-*.jar --num-threads 10 --timeout-seconds $TIMEOUT --num-queries $NUM_QUERIES --username default --password "" clickhouse --oracle TLPWhere | tee /test_output/TLPWhere.out ) 3>&1 1>&2 2>&3 | tee /test_output/TLPWhere.err -( java -jar target/sqlancer-*.jar --num-threads 10 --timeout-seconds $TIMEOUT --num-queries $NUM_QUERIES --username default --password "" clickhouse --oracle TLPGroupBy | tee /test_output/TLPGroupBy.out ) 3>&1 1>&2 2>&3 | tee /test_output/TLPGroupBy.err -( java -jar target/sqlancer-*.jar --num-threads 10 --timeout-seconds $TIMEOUT --num-queries $NUM_QUERIES --username default --password "" clickhouse --oracle TLPHaving | tee /test_output/TLPHaving.out ) 3>&1 1>&2 2>&3 | tee /test_output/TLPHaving.err -( java -jar target/sqlancer-*.jar --num-threads 10 --timeout-seconds $TIMEOUT --num-queries $NUM_QUERIES --username default --password "" clickhouse --oracle TLPWhere --oracle TLPGroupBy | tee /test_output/TLPWhereGroupBy.out ) 3>&1 1>&2 2>&3 | tee /test_output/TLPWhereGroupBy.err -( java -jar target/sqlancer-*.jar --num-threads 10 --timeout-seconds $TIMEOUT --num-queries $NUM_QUERIES --username default --password "" clickhouse --oracle TLPDistinct | tee /test_output/TLPDistinct.out ) 3>&1 1>&2 2>&3 | tee /test_output/TLPDistinct.err -( java -jar target/sqlancer-*.jar --num-threads 10 --timeout-seconds $TIMEOUT --num-queries $NUM_QUERIES --username default --password "" clickhouse --oracle TLPAggregate | tee /test_output/TLPAggregate.out ) 3>&1 1>&2 2>&3 | tee /test_output/TLPAggregate.err +( java -jar target/sqlancer-*.jar --num-threads 10 --timeout-seconds $TIMEOUT --num-queries $NUM_QUERIES --username default --password "" clickhouse --oracle TLPWhere | tee /workspace/TLPWhere.out ) 3>&1 1>&2 2>&3 | tee /workspace/TLPWhere.err +( java -jar target/sqlancer-*.jar --num-threads 10 --timeout-seconds $TIMEOUT --num-queries $NUM_QUERIES --username default --password "" clickhouse --oracle TLPGroupBy | tee /workspace/TLPGroupBy.out ) 3>&1 1>&2 2>&3 | tee /workspace/TLPGroupBy.err +( java -jar target/sqlancer-*.jar --num-threads 10 --timeout-seconds $TIMEOUT --num-queries $NUM_QUERIES --username default --password "" clickhouse --oracle TLPHaving | tee /workspace/TLPHaving.out ) 3>&1 1>&2 2>&3 | tee /workspace/TLPHaving.err +( java -jar target/sqlancer-*.jar --num-threads 10 --timeout-seconds $TIMEOUT --num-queries $NUM_QUERIES --username default --password "" clickhouse --oracle TLPWhere --oracle TLPGroupBy | tee /workspace/TLPWhereGroupBy.out ) 3>&1 1>&2 2>&3 | tee /workspace/TLPWhereGroupBy.err +( java -jar target/sqlancer-*.jar --num-threads 10 --timeout-seconds $TIMEOUT --num-queries $NUM_QUERIES --username default --password "" clickhouse --oracle TLPDistinct | tee /workspace/TLPDistinct.out ) 3>&1 1>&2 2>&3 | tee /workspace/TLPDistinct.err +( java -jar target/sqlancer-*.jar --num-threads 10 --timeout-seconds $TIMEOUT --num-queries $NUM_QUERIES --username default --password "" clickhouse --oracle TLPAggregate | tee /workspace/TLPAggregate.out ) 3>&1 1>&2 2>&3 | tee /workspace/TLPAggregate.err pkill -F /clickhouse-server.pid @@ -55,5 +55,6 @@ tail -n 1000 /var/log/clickhouse-server/stderr.log > /workplace/stderr.log tail -n 1000 /var/log/clickhouse-server/stdout.log > /workplace/stdout.log tail -n 1000 /var/log/clickhouse-server/clickhouse-server.log > /workplace/clickhouse-server.log +for f in $(ls /workspace/) /process_sqlancer_result.py || echo -e "failure\tCannot parse results" > /workplace/check_status.tsv ls /workplace From 6f26816d4fc7439fc7acadbc7eb7f836347e047b Mon Sep 17 00:00:00 2001 From: Yatsishin Ilya <2159081+qoega@users.noreply.github.com> Date: Mon, 17 Oct 2022 16:31:19 +0000 Subject: [PATCH 062/266] fix --- docker/test/sqlancer/run.sh | 1 - 1 file changed, 1 deletion(-) diff --git a/docker/test/sqlancer/run.sh b/docker/test/sqlancer/run.sh index 6a479db8fdd..ca0178c42b2 100755 --- a/docker/test/sqlancer/run.sh +++ b/docker/test/sqlancer/run.sh @@ -55,6 +55,5 @@ tail -n 1000 /var/log/clickhouse-server/stderr.log > /workplace/stderr.log tail -n 1000 /var/log/clickhouse-server/stdout.log > /workplace/stdout.log tail -n 1000 /var/log/clickhouse-server/clickhouse-server.log > /workplace/clickhouse-server.log -for f in $(ls /workspace/) /process_sqlancer_result.py || echo -e "failure\tCannot parse results" > /workplace/check_status.tsv ls /workplace From b8193a0eb3934d291e4f6abbd569073008db5bf4 Mon Sep 17 00:00:00 2001 From: Yatsishin Ilya <2159081+qoega@users.noreply.github.com> Date: Mon, 17 Oct 2022 17:42:18 +0000 Subject: [PATCH 063/266] one more time --- docker/test/sqlancer/run.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docker/test/sqlancer/run.sh b/docker/test/sqlancer/run.sh index ca0178c42b2..63b2aee1d09 100755 --- a/docker/test/sqlancer/run.sh +++ b/docker/test/sqlancer/run.sh @@ -47,7 +47,7 @@ export NUM_QUERIES=1000 pkill -F /clickhouse-server.pid -for i in $(seq 1 30); do if [[ $(wget -q 'localhost:8123' -O-) == 'Ok.' ]]; then sleep 1 ; else break; fi ; done +for _ in $(seq 1 30); do if [[ $(wget -q 'localhost:8123' -O-) == 'Ok.' ]]; then sleep 1 ; else break; fi ; done ls /var/log/clickhouse-server/ tar czf /workplace/logs.tar.gz -C /var/log/clickhouse-server/ . From 10acf43db4d863e8991c5195dfc37bb069d78c56 Mon Sep 17 00:00:00 2001 From: xiedeyantu Date: Tue, 18 Oct 2022 09:20:59 +0800 Subject: [PATCH 064/266] fix test --- tests/integration/test_partition/test.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/integration/test_partition/test.py b/tests/integration/test_partition/test.py index 18e59becdd4..f333011fa37 100644 --- a/tests/integration/test_partition/test.py +++ b/tests/integration/test_partition/test.py @@ -379,7 +379,7 @@ def test_system_detached_parts(drop_detached_parts_table): ) res = q( - "select system.detached_parts.* except `path`, disk from system.detached_parts where table like 'sdp_%' order by table, name" + "select system.detached_parts.* except (bytes_on_disk, `path`, disk) from system.detached_parts where table like 'sdp_%' order by table, name" ) assert ( res == "default\tsdp_0\tall\tall_1_1_0\tdefault\t\t1\t1\t0\n" From 163b3c9f5263e810aba2998487af45839d5432e6 Mon Sep 17 00:00:00 2001 From: chen Date: Tue, 18 Oct 2022 14:53:21 +0800 Subject: [PATCH 065/266] fix test fix test --- tests/integration/test_partition/test.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/integration/test_partition/test.py b/tests/integration/test_partition/test.py index f333011fa37..03c4ffbbd36 100644 --- a/tests/integration/test_partition/test.py +++ b/tests/integration/test_partition/test.py @@ -379,7 +379,7 @@ def test_system_detached_parts(drop_detached_parts_table): ) res = q( - "select system.detached_parts.* except (bytes_on_disk, `path`, disk) from system.detached_parts where table like 'sdp_%' order by table, name" + "select system.detached_parts.* except (bytes_on_disk, `path`) from system.detached_parts where table like 'sdp_%' order by table, name" ) assert ( res == "default\tsdp_0\tall\tall_1_1_0\tdefault\t\t1\t1\t0\n" From a50c0a7f85f7cef911a946d4ba9a54b61023ad97 Mon Sep 17 00:00:00 2001 From: Nikolay Degterinsky Date: Tue, 18 Oct 2022 07:23:00 +0000 Subject: [PATCH 066/266] Better test --- .../02457_tuple_of_intervals.reference | 21 +++++++--- .../0_stateless/02457_tuple_of_intervals.sql | 42 ++++++++++++++----- 2 files changed, 48 insertions(+), 15 deletions(-) diff --git a/tests/queries/0_stateless/02457_tuple_of_intervals.reference b/tests/queries/0_stateless/02457_tuple_of_intervals.reference index dd190dce891..d5ffbc33dc0 100644 --- a/tests/queries/0_stateless/02457_tuple_of_intervals.reference +++ b/tests/queries/0_stateless/02457_tuple_of_intervals.reference @@ -1,16 +1,27 @@ SELECT (toIntervalSecond(-1), toIntervalMinute(2), toIntervalMonth(-3), toIntervalYear(1)) --- +-1 +2022-10-12 +2022-10-10 +(2) +(0) +2022-10-12 +2022-10-10 +2022-10-12 +(2) Tuple(IntervalSecond) +(0) Tuple(IntervalSecond) +--- 3 IntervalSecond (1,2) Tuple(IntervalHour, IntervalSecond) (1,1,1) Tuple(IntervalSecond, IntervalHour, IntervalSecond) (2,1) Tuple(IntervalSecond, IntervalHour) ---- -3 IntervalSecond (-1,-2) Tuple(IntervalHour, IntervalSecond) (-1,-1,-1) Tuple(IntervalSecond, IntervalHour, IntervalSecond) (-2,-1) Tuple(IntervalSecond, IntervalHour) --- -1 -1 -1 -1 +1 2022-03-01 +1 2022-02-28 +1 2023-07-11 00:01:59 +1 2021-07-31 23:00:00 +1 2021-06-10 23:59:59.000 diff --git a/tests/queries/0_stateless/02457_tuple_of_intervals.sql b/tests/queries/0_stateless/02457_tuple_of_intervals.sql index d4065ab98f8..494914d4d4f 100644 --- a/tests/queries/0_stateless/02457_tuple_of_intervals.sql +++ b/tests/queries/0_stateless/02457_tuple_of_intervals.sql @@ -2,13 +2,29 @@ EXPLAIN SYNTAX SELECT INTERVAL '-1 SECOND 2 MINUTE -3 MONTH 1 YEAR'; SELECT '---'; +SELECT negate(INTERVAL 1 SECOND); +SELECT addTupleOfIntervals('2022-10-11'::Date, tuple(INTERVAL 1 DAY)); +SELECT subtractTupleOfIntervals('2022-10-11'::Date, tuple(INTERVAL 1 DAY)); +SELECT addInterval(tuple(INTERVAL 1 SECOND), INTERVAL 1 SECOND); +SELECT subtractInterval(tuple(INTERVAL 1 SECOND), INTERVAL 1 SECOND); + +SELECT '2022-10-11'::Date + tuple(INTERVAL 1 DAY); +SELECT '2022-10-11'::Date - tuple(INTERVAL 1 DAY); +SELECT tuple(INTERVAL 1 DAY) + '2022-10-11'::Date; +SELECT tuple(INTERVAL 1 DAY) - '2022-10-11'::Date; -- { serverError 43 } + +WITH tuple(INTERVAL 1 SECOND) + INTERVAL 1 SECOND as expr SELECT expr, toTypeName(expr); +WITH tuple(INTERVAL 1 SECOND) - INTERVAL 1 SECOND as expr SELECT expr, toTypeName(expr); +WITH INTERVAL 1 SECOND + tuple(INTERVAL 1 SECOND) as expr SELECT expr, toTypeName(expr); -- { serverError 43 } +WITH INTERVAL 1 SECOND - tuple(INTERVAL 1 SECOND) as expr SELECT expr, toTypeName(expr); -- { serverError 43 } + +SELECT '---'; + WITH INTERVAL 1 SECOND + INTERVAL 1 SECOND + INTERVAL 1 SECOND as expr SELECT expr, toTypeName(expr); WITH INTERVAL 1 HOUR + INTERVAL 1 SECOND + INTERVAL 1 SECOND as expr SELECT expr, toTypeName(expr); WITH INTERVAL 1 SECOND + INTERVAL 1 HOUR + INTERVAL 1 SECOND as expr SELECT expr, toTypeName(expr); WITH INTERVAL 1 SECOND + INTERVAL 1 SECOND + INTERVAL 1 HOUR as expr SELECT expr, toTypeName(expr); -SELECT '---'; - WITH - INTERVAL 1 SECOND - INTERVAL 1 SECOND - INTERVAL 1 SECOND as expr SELECT expr, toTypeName(expr); WITH - INTERVAL 1 HOUR - INTERVAL 1 SECOND - INTERVAL 1 SECOND as expr SELECT expr, toTypeName(expr); WITH - INTERVAL 1 SECOND - INTERVAL 1 HOUR - INTERVAL 1 SECOND as expr SELECT expr, toTypeName(expr); @@ -16,27 +32,33 @@ WITH - INTERVAL 1 SECOND - INTERVAL 1 SECOND - INTERVAL 1 HOUR as expr SELECT ex SELECT '---'; -WITH '2022-10-11'::Date + INTERVAL 1 DAY + INTERVAL 1 MONTH AS e1, - '2022-10-11'::Date + (INTERVAL 1 DAY + INTERVAL 1 MONTH) AS e2, - '2022-10-11'::Date + (INTERVAL 1 DAY, INTERVAL 1 MONTH) AS e3, - '2022-10-11'::Date + INTERVAL '1 DAY 1 MONTH' AS e4 -SELECT e1 == e2 AND e2 == e3 AND e3 == e4; +WITH '2022-01-30'::Date + INTERVAL 1 MONTH + INTERVAL 1 DAY AS e1, + '2022-01-30'::Date + (INTERVAL 1 MONTH + INTERVAL 1 DAY) AS e2, + '2022-01-30'::Date + (INTERVAL 1 MONTH, INTERVAL 1 DAY) AS e3, + '2022-01-30'::Date + INTERVAL '1 MONTH 1 DAY' AS e4 +SELECT e1 == e2 AND e2 == e3 AND e3 == e4, e1; + +WITH '2022-01-30'::Date + INTERVAL 1 DAY + INTERVAL 1 MONTH AS e1, + '2022-01-30'::Date + (INTERVAL 1 DAY + INTERVAL 1 MONTH) AS e2, + '2022-01-30'::Date + (INTERVAL 1 DAY, INTERVAL 1 MONTH) AS e3, + '2022-01-30'::Date + INTERVAL '1 DAY 1 MONTH' AS e4 +SELECT e1 == e2 AND e2 == e3 AND e3 == e4, e1; WITH '2022-10-11'::Date + INTERVAL -1 SECOND + INTERVAL 2 MINUTE + INTERVAL -3 MONTH + INTERVAL 1 YEAR AS e1, '2022-10-11'::Date + (INTERVAL -1 SECOND + INTERVAL 2 MINUTE + INTERVAL -3 MONTH + INTERVAL 1 YEAR) AS e2, '2022-10-11'::Date + (INTERVAL -1 SECOND, INTERVAL 2 MINUTE, INTERVAL -3 MONTH, INTERVAL 1 YEAR) AS e3, '2022-10-11'::Date + INTERVAL '-1 SECOND 2 MINUTE -3 MONTH 1 YEAR' AS e4 -SELECT e1 == e2 AND e2 == e3 AND e3 == e4; +SELECT e1 == e2 AND e2 == e3 AND e3 == e4, e1; WITH '2022-10-11'::DateTime - INTERVAL 1 QUARTER - INTERVAL -3 WEEK - INTERVAL 1 YEAR - INTERVAL 1 HOUR AS e1, '2022-10-11'::DateTime + (- INTERVAL 1 QUARTER - INTERVAL -3 WEEK - INTERVAL 1 YEAR - INTERVAL 1 HOUR) AS e2, '2022-10-11'::DateTime - (INTERVAL 1 QUARTER, INTERVAL -3 WEEK, INTERVAL 1 YEAR, INTERVAL 1 HOUR) AS e3, '2022-10-11'::DateTime - INTERVAL '1 QUARTER -3 WEEK 1 YEAR 1 HOUR' AS e4 -SELECT e1 == e2 AND e2 == e3 AND e3 == e4; +SELECT e1 == e2 AND e2 == e3 AND e3 == e4, e1; WITH '2022-10-11'::DateTime64 - INTERVAL 1 YEAR - INTERVAL 4 MONTH - INTERVAL 1 SECOND AS e1, '2022-10-11'::DateTime64 + (- INTERVAL 1 YEAR - INTERVAL 4 MONTH - INTERVAL 1 SECOND) AS e2, '2022-10-11'::DateTime64 - (INTERVAL 1 YEAR, INTERVAL 4 MONTH, INTERVAL 1 SECOND) AS e3, '2022-10-11'::DateTime64 - INTERVAL '1 YEAR 4 MONTH 1 SECOND' AS e4 -SELECT e1 == e2 AND e2 == e3 AND e3 == e4; \ No newline at end of file +SELECT e1 == e2 AND e2 == e3 AND e3 == e4, e1; \ No newline at end of file From 8b3dc2d551e2ce2824c4ab7f9ab841282ac0a5df Mon Sep 17 00:00:00 2001 From: Yatsishin Ilya <2159081+qoega@users.noreply.github.com> Date: Tue, 18 Oct 2022 07:52:11 +0000 Subject: [PATCH 067/266] typo --- tests/ci/ci_config.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/ci/ci_config.py b/tests/ci/ci_config.py index 4b49a088a37..7c5a5a56e42 100644 --- a/tests/ci/ci_config.py +++ b/tests/ci/ci_config.py @@ -355,7 +355,7 @@ CI_CONFIG = { "required_build": "package_aarch64", "test_grep_exclude_filter": "", }, - "SQLancer (debug)": { + "SQLancer (release)": { "required_build": "package_release", }, }, From 757959d087e1abb881246327c6e39acb8e1ad198 Mon Sep 17 00:00:00 2001 From: Yatsishin Ilya <2159081+qoega@users.noreply.github.com> Date: Tue, 18 Oct 2022 15:14:42 +0000 Subject: [PATCH 068/266] fix bug --- tests/ci/ast_fuzzer_check.py | 2 +- tests/ci/sqlancer_check.py | 38 +++++++++++++++++------------------- 2 files changed, 19 insertions(+), 21 deletions(-) diff --git a/tests/ci/ast_fuzzer_check.py b/tests/ci/ast_fuzzer_check.py index 8f94ef4a915..d6be5648d67 100644 --- a/tests/ci/ast_fuzzer_check.py +++ b/tests/ci/ast_fuzzer_check.py @@ -69,7 +69,7 @@ if __name__ == "__main__": logging.info("Check is already finished according to github status, exiting") sys.exit(0) - docker_image = get_image_with_version(temp_path, IMAGE_NAME) + docker_image = get_image_with_version(get_image_with_version, IMAGE_NAME) build_name = get_build_name_for_check(check_name) print(build_name) diff --git a/tests/ci/sqlancer_check.py b/tests/ci/sqlancer_check.py index 55660e4877b..66511aebe6d 100644 --- a/tests/ci/sqlancer_check.py +++ b/tests/ci/sqlancer_check.py @@ -69,7 +69,7 @@ if __name__ == "__main__": logging.info("Check is already finished according to github status, exiting") sys.exit(0) - docker_image = get_image_with_version(temp_path, IMAGE_NAME) + docker_image = get_image_with_version(reports_path, IMAGE_NAME) build_name = get_build_name_for_check(check_name) print(build_name) @@ -144,27 +144,25 @@ if __name__ == "__main__": if paths["report.html"]: report_url = paths["report.html"] - # # Try to get status message saved by the SQLancer - # try: - # with open( - # os.path.join(workspace_path, "status.txt"), "r", encoding="utf-8" - # ) as status_f: - # status = status_f.readline().rstrip("\n") + # Try to get status message saved by the SQLancer + try: + with open( + os.path.join(workspace_path, "status.txt"), "r", encoding="utf-8" + ) as status_f: + status = status_f.readline().rstrip("\n") - # with open( - # os.path.join(workspace_path, "description.txt"), "r", encoding="utf-8" - # ) as desc_f: - # description = desc_f.readline().rstrip("\n")[:140] - # except: - # status = "failure" - # description = "Task failed: $?=" + str(retcode) + with open( + os.path.join(workspace_path, "description.txt"), "r", encoding="utf-8" + ) as desc_f: + description = desc_f.readline().rstrip("\n")[:140] + except: + status = "failure" + description = "Task failed: $?=" + str(retcode) - # if "fail" in status: - # test_result = [(description, "FAIL")] - # else: - status = "success" - description = "SQLancer runs in always green mode for now" - test_result = [(check_name, "OK")] + if "fail" in status: + test_result = [(description, "FAIL")] + else: + status = "success" ch_helper = ClickHouseHelper() From 32ad28cbe507aadea9f16df70d01129b1ef15d0b Mon Sep 17 00:00:00 2001 From: xiedeyantu Date: Wed, 19 Oct 2022 17:37:26 +0800 Subject: [PATCH 069/266] optimize code --- .../MergeTree/DataPartStorageOnDisk.cpp | 5 -- .../MergeTree/DataPartStorageOnDisk.h | 2 - .../System/StorageSystemDetachedParts.cpp | 47 ++++++++++++++----- 3 files changed, 35 insertions(+), 19 deletions(-) diff --git a/src/Storages/MergeTree/DataPartStorageOnDisk.cpp b/src/Storages/MergeTree/DataPartStorageOnDisk.cpp index e50cee4b654..e2a2f3f793f 100644 --- a/src/Storages/MergeTree/DataPartStorageOnDisk.cpp +++ b/src/Storages/MergeTree/DataPartStorageOnDisk.cpp @@ -121,11 +121,6 @@ static UInt64 calculateTotalSizeOnDiskImpl(const DiskPtr & disk, const String & return res; } -UInt64 DataPartStorageOnDisk::calculateTotalSizeOnDisk(const DiskPtr & disk, const String & from) -{ - return calculateTotalSizeOnDiskImpl(disk, from); -} - UInt64 DataPartStorageOnDisk::calculateTotalSizeOnDisk() const { return calculateTotalSizeOnDiskImpl(volume->getDisk(), fs::path(root_path) / part_dir); diff --git a/src/Storages/MergeTree/DataPartStorageOnDisk.h b/src/Storages/MergeTree/DataPartStorageOnDisk.h index 79988e9baab..91ed8bbfb43 100644 --- a/src/Storages/MergeTree/DataPartStorageOnDisk.h +++ b/src/Storages/MergeTree/DataPartStorageOnDisk.h @@ -113,8 +113,6 @@ public: DataPartStorageBuilderPtr getBuilder() const override; - static UInt64 calculateTotalSizeOnDisk(const DiskPtr & disk, const String & from); - private: VolumePtr volume; std::string root_path; diff --git a/src/Storages/System/StorageSystemDetachedParts.cpp b/src/Storages/System/StorageSystemDetachedParts.cpp index 53526a02459..980529032c4 100644 --- a/src/Storages/System/StorageSystemDetachedParts.cpp +++ b/src/Storages/System/StorageSystemDetachedParts.cpp @@ -31,6 +31,40 @@ StorageSystemDetachedParts::StorageSystemDetachedParts(const StorageID & table_i }}); setInMemoryMetadata(storage_metadata); } +static void calculateTotalSizeOnDiskImpl(const DiskPtr & disk, const String & from, UInt64 & total_size) +{ + /// Files or directories of detached part may not exist. Only count the size of existing files. + if (disk->isFile(from)) + { + try + { + total_size += disk->getFileSize(from); + } + catch (...) + { + tryLogCurrentException(__PRETTY_FUNCTION__); + } + return; + } + std::vector files; + try + { + disk->listFiles(from, files); + } + catch (...) + { + tryLogCurrentException(__PRETTY_FUNCTION__); + } + for (const auto & file : files) + calculateTotalSizeOnDiskImpl(disk, fs::path(from) / file, total_size); +} + +static UInt64 calculateTotalSizeOnDisk(const DiskPtr & disk, const String & from) +{ + UInt64 total_size = 0; + calculateTotalSizeOnDiskImpl(disk, from, total_size); + return total_size; +} Pipe StorageSystemDetachedParts::read( const Names & /* column_names */, @@ -58,18 +92,7 @@ Pipe StorageSystemDetachedParts::read( new_columns[i++]->insert(info.table); new_columns[i++]->insert(p.valid_name ? p.partition_id : Field()); new_columns[i++]->insert(p.dir_name); - - UInt64 bytes_on_disk = 0; - try - { - /// Files of detached part may be not exist, and then set file size is 0. - bytes_on_disk = DataPartStorageOnDisk::calculateTotalSizeOnDisk( - p.disk, fs::path(info.data->getRelativeDataPath()) / detached_part_path); - } - catch (...) - {} - - new_columns[i++]->insert(bytes_on_disk); + new_columns[i++]->insert(calculateTotalSizeOnDisk(p.disk, fs::path(info.data->getRelativeDataPath()) / detached_part_path)); new_columns[i++]->insert(p.disk->getName()); new_columns[i++]->insert((fs::path(info.data->getFullPathOnDisk(p.disk)) / detached_part_path).string()); new_columns[i++]->insert(p.valid_name ? p.prefix : Field()); From 330777a0ccf787eeff887a72892cf163ff0efb1c Mon Sep 17 00:00:00 2001 From: xiedeyantu Date: Wed, 19 Oct 2022 17:44:36 +0800 Subject: [PATCH 070/266] fix --- src/Storages/MergeTree/DataPartStorageOnDisk.h | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/Storages/MergeTree/DataPartStorageOnDisk.h b/src/Storages/MergeTree/DataPartStorageOnDisk.h index 91ed8bbfb43..adf1b78cdfb 100644 --- a/src/Storages/MergeTree/DataPartStorageOnDisk.h +++ b/src/Storages/MergeTree/DataPartStorageOnDisk.h @@ -112,7 +112,6 @@ public: void changeRootPath(const std::string & from_root, const std::string & to_root) override; DataPartStorageBuilderPtr getBuilder() const override; - private: VolumePtr volume; std::string root_path; @@ -128,7 +127,6 @@ private: MergeTreeDataPartState state, Poco::Logger * log, bool is_projection) const; - }; class DataPartStorageBuilderOnDisk final : public IDataPartStorageBuilder From 5121e9b09632f2a4a78c0be225936d68b471349e Mon Sep 17 00:00:00 2001 From: Yatsishin Ilya <2159081+qoega@users.noreply.github.com> Date: Wed, 19 Oct 2022 16:30:01 +0000 Subject: [PATCH 071/266] update docker scripts --- .../test/sqlancer/process_sqlancer_result.py | 5 ---- docker/test/sqlancer/run.sh | 30 ++++++++----------- 2 files changed, 13 insertions(+), 22 deletions(-) diff --git a/docker/test/sqlancer/process_sqlancer_result.py b/docker/test/sqlancer/process_sqlancer_result.py index 5721349b6e5..9e40f01c885 100755 --- a/docker/test/sqlancer/process_sqlancer_result.py +++ b/docker/test/sqlancer/process_sqlancer_result.py @@ -37,11 +37,6 @@ def process_result(result_folder): else: summary.append((test, "OK")) - logs_path = "{}/logs.tar.gz".format(result_folder) - if not os.path.exists(logs_path): - logging.info("No logs tar on path %s", logs_path) - else: - paths.append(logs_path) stdout_path = "{}/stdout.log".format(result_folder) if not os.path.exists(stdout_path): logging.info("No stdout log on path %s", stdout_path) diff --git a/docker/test/sqlancer/run.sh b/docker/test/sqlancer/run.sh index 63b2aee1d09..8476afaefb9 100755 --- a/docker/test/sqlancer/run.sh +++ b/docker/test/sqlancer/run.sh @@ -28,8 +28,8 @@ else exit 1 fi - -/clickhouse server -P /clickhouse-server.pid -L /clickhouse-server.log -E /clickhouse-server.log.err --daemon +cd /workspace +/clickhouse server -P /workspace/clickhouse-server.pid -L /workspace/clickhouse-server.log -E /workspace/clickhouse-server.log.err --daemon for _ in $(seq 1 30); do if [[ $(wget -q 'localhost:8123' -O-) == 'Ok.' ]]; then break ; else sleep 1; fi ; done @@ -37,23 +37,19 @@ cd /sqlancer/sqlancer-master export TIMEOUT=300 export NUM_QUERIES=1000 +export NUM_THREADS=30 -( java -jar target/sqlancer-*.jar --num-threads 10 --timeout-seconds $TIMEOUT --num-queries $NUM_QUERIES --username default --password "" clickhouse --oracle TLPWhere | tee /workspace/TLPWhere.out ) 3>&1 1>&2 2>&3 | tee /workspace/TLPWhere.err -( java -jar target/sqlancer-*.jar --num-threads 10 --timeout-seconds $TIMEOUT --num-queries $NUM_QUERIES --username default --password "" clickhouse --oracle TLPGroupBy | tee /workspace/TLPGroupBy.out ) 3>&1 1>&2 2>&3 | tee /workspace/TLPGroupBy.err -( java -jar target/sqlancer-*.jar --num-threads 10 --timeout-seconds $TIMEOUT --num-queries $NUM_QUERIES --username default --password "" clickhouse --oracle TLPHaving | tee /workspace/TLPHaving.out ) 3>&1 1>&2 2>&3 | tee /workspace/TLPHaving.err -( java -jar target/sqlancer-*.jar --num-threads 10 --timeout-seconds $TIMEOUT --num-queries $NUM_QUERIES --username default --password "" clickhouse --oracle TLPWhere --oracle TLPGroupBy | tee /workspace/TLPWhereGroupBy.out ) 3>&1 1>&2 2>&3 | tee /workspace/TLPWhereGroupBy.err -( java -jar target/sqlancer-*.jar --num-threads 10 --timeout-seconds $TIMEOUT --num-queries $NUM_QUERIES --username default --password "" clickhouse --oracle TLPDistinct | tee /workspace/TLPDistinct.out ) 3>&1 1>&2 2>&3 | tee /workspace/TLPDistinct.err -( java -jar target/sqlancer-*.jar --num-threads 10 --timeout-seconds $TIMEOUT --num-queries $NUM_QUERIES --username default --password "" clickhouse --oracle TLPAggregate | tee /workspace/TLPAggregate.out ) 3>&1 1>&2 2>&3 | tee /workspace/TLPAggregate.err +( java -jar target/sqlancer-*.jar --num-threads $NUM_THREADS --timeout-seconds $TIMEOUT --num-queries $NUM_QUERIES --username default --password "" clickhouse --oracle TLPWhere | tee /workspace/TLPWhere.out ) 3>&1 1>&2 2>&3 | tee /workspace/TLPWhere.err +( java -jar target/sqlancer-*.jar --num-threads $NUM_THREADS --timeout-seconds $TIMEOUT --num-queries $NUM_QUERIES --username default --password "" clickhouse --oracle TLPGroupBy | tee /workspace/TLPGroupBy.out ) 3>&1 1>&2 2>&3 | tee /workspace/TLPGroupBy.err +( java -jar target/sqlancer-*.jar --num-threads $NUM_THREADS --timeout-seconds $TIMEOUT --num-queries $NUM_QUERIES --username default --password "" clickhouse --oracle TLPHaving | tee /workspace/TLPHaving.out ) 3>&1 1>&2 2>&3 | tee /workspace/TLPHaving.err +( java -jar target/sqlancer-*.jar --num-threads $NUM_THREADS --timeout-seconds $TIMEOUT --num-queries $NUM_QUERIES --username default --password "" clickhouse --oracle TLPWhere --oracle TLPGroupBy | tee /workspace/TLPWhereGroupBy.out ) 3>&1 1>&2 2>&3 | tee /workspace/TLPWhereGroupBy.err +( java -jar target/sqlancer-*.jar --num-threads $NUM_THREADS --timeout-seconds $TIMEOUT --num-queries $NUM_QUERIES --username default --password "" clickhouse --oracle TLPDistinct | tee /workspace/TLPDistinct.out ) 3>&1 1>&2 2>&3 | tee /workspace/TLPDistinct.err +( java -jar target/sqlancer-*.jar --num-threads $NUM_THREADS --timeout-seconds $TIMEOUT --num-queries $NUM_QUERIES --username default --password "" clickhouse --oracle TLPAggregate | tee /workspace/TLPAggregate.out ) 3>&1 1>&2 2>&3 | tee /workspace/TLPAggregate.err -pkill -F /clickhouse-server.pid +ls /workspace +pkill -F /workspace/clickhouse-server.pid for _ in $(seq 1 30); do if [[ $(wget -q 'localhost:8123' -O-) == 'Ok.' ]]; then sleep 1 ; else break; fi ; done -ls /var/log/clickhouse-server/ -tar czf /workplace/logs.tar.gz -C /var/log/clickhouse-server/ . -tail -n 1000 /var/log/clickhouse-server/stderr.log > /workplace/stderr.log -tail -n 1000 /var/log/clickhouse-server/stdout.log > /workplace/stdout.log -tail -n 1000 /var/log/clickhouse-server/clickhouse-server.log > /workplace/clickhouse-server.log - -/process_sqlancer_result.py || echo -e "failure\tCannot parse results" > /workplace/check_status.tsv -ls /workplace +/process_sqlancer_result.py || echo -e "failure\tCannot parse results" > /workspace/check_status.tsv +ls /workspace From 7d90d54af8b394312456abf909e9f9a7169c1756 Mon Sep 17 00:00:00 2001 From: Yatsishin Ilya <2159081+qoega@users.noreply.github.com> Date: Wed, 19 Oct 2022 17:02:49 +0000 Subject: [PATCH 072/266] better reporting --- .../test/sqlancer/process_sqlancer_result.py | 25 ++++++----- tests/ci/sqlancer_check.py | 44 +++++++++---------- 2 files changed, 37 insertions(+), 32 deletions(-) diff --git a/docker/test/sqlancer/process_sqlancer_result.py b/docker/test/sqlancer/process_sqlancer_result.py index 9e40f01c885..dda52b782c8 100755 --- a/docker/test/sqlancer/process_sqlancer_result.py +++ b/docker/test/sqlancer/process_sqlancer_result.py @@ -19,6 +19,8 @@ def process_result(result_folder): "TLPAggregate", ] + failed_tests= [] + for test in tests: err_path = "{}/{}.err".format(result_folder, test) out_path = "{}/{}.out".format(result_folder, test) @@ -33,6 +35,7 @@ def process_result(result_folder): with open(err_path, "r") as f: if "AssertionError" in f.read(): summary.append((test, "FAIL")) + failed_tests.append(test) status = "failure" else: summary.append((test, "OK")) @@ -48,19 +51,21 @@ def process_result(result_folder): else: paths.append(stderr_path) - description = "SQLancer test run. See report" + description = "SQLancer run successfully" + if status == "failure": + description = f"Failed oracles: {failed_tests}" return status, description, summary, paths -def write_results(results_file, status_file, results, status): +def write_results(results_file, status_file, description_file, results, status, description): with open(results_file, "w") as f: out = csv.writer(f, delimiter="\t") out.writerows(results) with open(status_file, "w") as f: - out = csv.writer(f, delimiter="\t") - out.writerow(status) - + f.write(status + '\n') + with open(description_file, "w") as f: + f.write(description + '\n') if __name__ == "__main__": logging.basicConfig(level=logging.INFO, format="%(asctime)s %(message)s") @@ -68,12 +73,12 @@ if __name__ == "__main__": description="ClickHouse script for parsing results of sqlancer test" ) parser.add_argument("--in-results-dir", default="/workspace/") - parser.add_argument("--out-results-file", default="/workspace/test_results.tsv") - parser.add_argument("--out-status-file", default="/workspace/check_status.tsv") + parser.add_argument("--out-results-file", default="/workspace/summary.tsv") + parser.add_argument("--out-description-file", default="/workspace/description.txt") + parser.add_argument("--out-status-file", default="/workspace/status.txt") args = parser.parse_args() - state, description, test_results, logs = process_result(args.in_results_dir) + status, description, summary, logs = process_result(args.in_results_dir) logging.info("Result parsed") - status = (state, description) - write_results(args.out_results_file, args.out_status_file, test_results, status) + write_results(args.out_results_file, args.out_status_file, args.out_description_file, summary, status, description) logging.info("Result written") diff --git a/tests/ci/sqlancer_check.py b/tests/ci/sqlancer_check.py index 66511aebe6d..cf71b9199f8 100644 --- a/tests/ci/sqlancer_check.py +++ b/tests/ci/sqlancer_check.py @@ -113,17 +113,23 @@ if __name__ == "__main__": paths = { "runlog.log": run_log_path, "TLPWhere": os.path.join(workspace_path, "TLPWhere.log"), - "TLPGroupBy": os.path.join(workspace_path, "TLPGroupBy.log"), - "TLPHaving": os.path.join(workspace_path, "TLPHaving.log"), - "TLPWhereGroupBy": os.path.join(workspace_path, "TLPWhereGroupBy.log"), - "TLPDistinct": os.path.join(workspace_path, "TLPDistinct.log"), - "TLPAggregate": os.path.join(workspace_path, "TLPAggregate.log"), + "TLPAggregate.err": os.path.join(workspace_path, "TLPAggregate.err"), + "TLPAggregate.out": os.path.join(workspace_path, "TLPAggregate.out"), + "TLPDistinct.err": os.path.join(workspace_path, "TLPDistinct.err"), + "TLPDistinct.out": os.path.join(workspace_path, "TLPDistinct.out"), + "TLPGroupBy.err": os.path.join(workspace_path, "TLPGroupBy.err"), + "TLPGroupBy.out": os.path.join(workspace_path, "TLPGroupBy.out"), + "TLPHaving.err": os.path.join(workspace_path, "TLPHaving.err"), + "TLPHaving.out": os.path.join(workspace_path, "TLPHaving.out"), + "TLPWhere.err": os.path.join(workspace_path, "TLPWhere.err"), + "TLPWhere.out": os.path.join(workspace_path, "TLPWhere.out"), + "TLPWhereGroupBy.err": os.path.join(workspace_path, "TLPWhereGroupBy.err"), + "TLPWhereGroupBy.out": os.path.join(workspace_path, "TLPWhereGroupBy.out"), + "clickhouse-server.log": os.path.join(workspace_path, "clickhouse-server"), + "clickhouse-server.log.err": os.path.join(workspace_path, "clickhouse-server"), "stderr.log": os.path.join(workspace_path, "stderr.log"), "stdout.log": os.path.join(workspace_path, "stdout.log"), - "clickhouse-server.log": os.path.join(workspace_path, "clickhouse-server.log"), - "check_status.tsv": os.path.join(workspace_path, "check_status.tsv"), - "report.html": os.path.join(workspace_path, "report.html"), - "logs.tar.gz": os.path.join(workspace_path, "logs.tar.gz"), + "test_results.tsv": os.path.join(workspace_path, "test_results.tsv"), } s3_helper = S3Helper() @@ -135,14 +141,6 @@ if __name__ == "__main__": paths[f] = "" report_url = GITHUB_RUN_URL - if paths["stderr.log"]: - report_url = paths["stderr.log"] - if paths["stdout.log"]: - report_url = paths["stdout.log"] - if paths["clickhouse-server.log"]: - report_url = paths["clickhouse-server.log"] - if paths["report.html"]: - report_url = paths["report.html"] # Try to get status message saved by the SQLancer try: @@ -151,6 +149,13 @@ if __name__ == "__main__": ) as status_f: status = status_f.readline().rstrip("\n") + with open( + os.path.join(workspace_path, "summary.tsv"), "r", encoding="utf-8" + ) as summary_f: + for line in summary_f: + l=line.split('\t') + test_result.append((l[0],l[1])) + with open( os.path.join(workspace_path, "description.txt"), "r", encoding="utf-8" ) as desc_f: @@ -159,11 +164,6 @@ if __name__ == "__main__": status = "failure" description = "Task failed: $?=" + str(retcode) - if "fail" in status: - test_result = [(description, "FAIL")] - else: - status = "success" - ch_helper = ClickHouseHelper() prepared_events = prepare_tests_results_for_clickhouse( From ab41c8e8ce456cf219351c79445757f4c8c32331 Mon Sep 17 00:00:00 2001 From: Yatsishin Ilya <2159081+qoega@users.noreply.github.com> Date: Wed, 19 Oct 2022 17:16:41 +0000 Subject: [PATCH 073/266] black --- .../test/sqlancer/process_sqlancer_result.py | 20 ++++++++++++++----- 1 file changed, 15 insertions(+), 5 deletions(-) diff --git a/docker/test/sqlancer/process_sqlancer_result.py b/docker/test/sqlancer/process_sqlancer_result.py index dda52b782c8..d0e41643467 100755 --- a/docker/test/sqlancer/process_sqlancer_result.py +++ b/docker/test/sqlancer/process_sqlancer_result.py @@ -19,7 +19,7 @@ def process_result(result_folder): "TLPAggregate", ] - failed_tests= [] + failed_tests = [] for test in tests: err_path = "{}/{}.err".format(result_folder, test) @@ -58,14 +58,17 @@ def process_result(result_folder): return status, description, summary, paths -def write_results(results_file, status_file, description_file, results, status, description): +def write_results( + results_file, status_file, description_file, results, status, description +): with open(results_file, "w") as f: out = csv.writer(f, delimiter="\t") out.writerows(results) with open(status_file, "w") as f: - f.write(status + '\n') + f.write(status + "\n") with open(description_file, "w") as f: - f.write(description + '\n') + f.write(description + "\n") + if __name__ == "__main__": logging.basicConfig(level=logging.INFO, format="%(asctime)s %(message)s") @@ -80,5 +83,12 @@ if __name__ == "__main__": status, description, summary, logs = process_result(args.in_results_dir) logging.info("Result parsed") - write_results(args.out_results_file, args.out_status_file, args.out_description_file, summary, status, description) + write_results( + args.out_results_file, + args.out_status_file, + args.out_description_file, + summary, + status, + description, + ) logging.info("Result written") From 8ef8def9abddd0e81514f475d23f36b28c26420c Mon Sep 17 00:00:00 2001 From: Ilya Yatsishin <2159081+qoega@users.noreply.github.com> Date: Wed, 19 Oct 2022 21:00:05 +0200 Subject: [PATCH 074/266] Update tests/ci/sqlancer_check.py --- tests/ci/sqlancer_check.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/ci/sqlancer_check.py b/tests/ci/sqlancer_check.py index cf71b9199f8..ec70951e033 100644 --- a/tests/ci/sqlancer_check.py +++ b/tests/ci/sqlancer_check.py @@ -153,8 +153,8 @@ if __name__ == "__main__": os.path.join(workspace_path, "summary.tsv"), "r", encoding="utf-8" ) as summary_f: for line in summary_f: - l=line.split('\t') - test_result.append((l[0],l[1])) + l = line.split('\t') + test_result.append((l[0], l[1])) with open( os.path.join(workspace_path, "description.txt"), "r", encoding="utf-8" From b768925256e52b3d0ab0e4254dcdb7d221ebd149 Mon Sep 17 00:00:00 2001 From: Ilya Yatsishin <2159081+qoega@users.noreply.github.com> Date: Thu, 20 Oct 2022 00:09:09 +0200 Subject: [PATCH 075/266] Update tests/ci/sqlancer_check.py --- tests/ci/sqlancer_check.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/ci/sqlancer_check.py b/tests/ci/sqlancer_check.py index ec70951e033..3cf4b42c873 100644 --- a/tests/ci/sqlancer_check.py +++ b/tests/ci/sqlancer_check.py @@ -153,7 +153,7 @@ if __name__ == "__main__": os.path.join(workspace_path, "summary.tsv"), "r", encoding="utf-8" ) as summary_f: for line in summary_f: - l = line.split('\t') + l = line.split("\t") test_result.append((l[0], l[1])) with open( From 105ca72955dbf3f082c34833f33c66291fddf957 Mon Sep 17 00:00:00 2001 From: Yatsishin Ilya <2159081+qoega@users.noreply.github.com> Date: Thu, 20 Oct 2022 10:51:47 +0000 Subject: [PATCH 076/266] fix --- tests/ci/sqlancer_check.py | 1 + 1 file changed, 1 insertion(+) diff --git a/tests/ci/sqlancer_check.py b/tests/ci/sqlancer_check.py index 3cf4b42c873..5367c302626 100644 --- a/tests/ci/sqlancer_check.py +++ b/tests/ci/sqlancer_check.py @@ -152,6 +152,7 @@ if __name__ == "__main__": with open( os.path.join(workspace_path, "summary.tsv"), "r", encoding="utf-8" ) as summary_f: + test_result = [] for line in summary_f: l = line.split("\t") test_result.append((l[0], l[1])) From 548ff1d2aaf348cd7f097892a26d13391543eb39 Mon Sep 17 00:00:00 2001 From: Yatsishin Ilya <2159081+qoega@users.noreply.github.com> Date: Thu, 20 Oct 2022 12:33:56 +0000 Subject: [PATCH 077/266] fix --- tests/ci/ast_fuzzer_check.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/ci/ast_fuzzer_check.py b/tests/ci/ast_fuzzer_check.py index d6be5648d67..04dbe78adc4 100644 --- a/tests/ci/ast_fuzzer_check.py +++ b/tests/ci/ast_fuzzer_check.py @@ -69,7 +69,7 @@ if __name__ == "__main__": logging.info("Check is already finished according to github status, exiting") sys.exit(0) - docker_image = get_image_with_version(get_image_with_version, IMAGE_NAME) + docker_image = get_image_with_version(reports_path, IMAGE_NAME) build_name = get_build_name_for_check(check_name) print(build_name) From 13f506f7b12a5d8246211440df9a20d8481368c9 Mon Sep 17 00:00:00 2001 From: Yatsishin Ilya <2159081+qoega@users.noreply.github.com> Date: Fri, 21 Oct 2022 11:48:58 +0000 Subject: [PATCH 078/266] minor --- tests/ci/ci_config.py | 3 +++ tests/ci/sqlancer_check.py | 19 ++++++++++++++++--- 2 files changed, 19 insertions(+), 3 deletions(-) diff --git a/tests/ci/ci_config.py b/tests/ci/ci_config.py index 7c5a5a56e42..93322b69669 100644 --- a/tests/ci/ci_config.py +++ b/tests/ci/ci_config.py @@ -358,6 +358,9 @@ CI_CONFIG = { "SQLancer (release)": { "required_build": "package_release", }, + "SQLancer (debug)": { + "required_build": "package_debug", + }, }, } # type: dict diff --git a/tests/ci/sqlancer_check.py b/tests/ci/sqlancer_check.py index 5367c302626..2f320fbbf71 100644 --- a/tests/ci/sqlancer_check.py +++ b/tests/ci/sqlancer_check.py @@ -129,7 +129,6 @@ if __name__ == "__main__": "clickhouse-server.log.err": os.path.join(workspace_path, "clickhouse-server"), "stderr.log": os.path.join(workspace_path, "stderr.log"), "stdout.log": os.path.join(workspace_path, "stdout.log"), - "test_results.tsv": os.path.join(workspace_path, "test_results.tsv"), } s3_helper = S3Helper() @@ -152,10 +151,10 @@ if __name__ == "__main__": with open( os.path.join(workspace_path, "summary.tsv"), "r", encoding="utf-8" ) as summary_f: - test_result = [] + test_results = [] for line in summary_f: l = line.split("\t") - test_result.append((l[0], l[1])) + test_results.append((l[0], l[1])) with open( os.path.join(workspace_path, "description.txt"), "r", encoding="utf-8" @@ -165,6 +164,20 @@ if __name__ == "__main__": status = "failure" description = "Task failed: $?=" + str(retcode) + report_url = upload_results( + s3_helper, + pr_info.number, + pr_info.sha, + test_results, + paths, + check_name, + False, + ) + + post_commit_status(gh, pr_info.sha, check_name, description, status, report_url) + + print(f"::notice:: {check_name} Report url: {report_url}") + ch_helper = ClickHouseHelper() prepared_events = prepare_tests_results_for_clickhouse( From 52ca29140e739f1a67caccb450ccda7c97f349b6 Mon Sep 17 00:00:00 2001 From: DanRoscigno Date: Fri, 21 Oct 2022 12:27:20 -0400 Subject: [PATCH 079/266] WIP: add Superset deploy instructions --- .../example-datasets/cell-towers.md | 24 +++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/docs/en/getting-started/example-datasets/cell-towers.md b/docs/en/getting-started/example-datasets/cell-towers.md index 3d993c3e224..989ae6f0a7e 100644 --- a/docs/en/getting-started/example-datasets/cell-towers.md +++ b/docs/en/getting-started/example-datasets/cell-towers.md @@ -10,6 +10,7 @@ import TabItem from '@theme/TabItem'; import CodeBlock from '@theme/CodeBlock'; import ActionsMenu from '@site/docs/en/_snippets/_service_actions_menu.md'; import SQLConsoleDetail from '@site/docs/en/_snippets/_launch_sql_console.md'; +import SupersetDocker from '@site/docs/en/_snippets/_add_superset_detail.md'; This dataset is from [OpenCellid](https://www.opencellid.org/) - The world's largest Open Database of Cell Towers. @@ -224,6 +225,29 @@ WHERE pointInPolygon((lon, lat), (SELECT * FROM moscow)) 1 rows in set. Elapsed: 0.067 sec. Processed 43.28 million rows, 692.42 MB (645.83 million rows/s., 10.33 GB/s.) ``` +## Review of the schema + +This dataset primarily provides the location (Longitude and Latitude) and radio types at mobile cellular towers worldwide. The column descriptions can be found in the [community forum](https://community.opencellid.org/t/documenting-the-columns-in-the-downloadable-cells-database-csv/186). The columns used in the visualizations that will be built are described below + +Here is a description of the columns taken from the OpenCellID forum: + +| Column | Description | +|--------------|--------------------------------------------------------| +| radio | Technology generation: CDMA, GSM, UMTS, 5G NR | +| mcc | Mobile Country Code: `204` is The Netherlands | +| lon | Longitude: With Latitude, approximate tower location | +| lat | Latitude: With Longitude, approximate tower location | + +:::tip mcc +To find your MCC check [Mobile network codes](https://en.wikipedia.org/wiki/Mobile_country_code), and use the first three digits in the `MCC / MNC` column. +::: + +## Build visualizations with Apache Superset + +Superset is easy to run from Docker. If you already have Superset running, all you need to do is add ClickHouse Connect with `pip install clickhouse-connect`. If you need to install Superset open the **Launch Apache Superset in Docker** directly below. + + + The data is also available for interactive queries in the [Playground](https://play.clickhouse.com/play?user=play), [example](https://play.clickhouse.com/play?user=play#U0VMRUNUIG1jYywgY291bnQoKSBGUk9NIGNlbGxfdG93ZXJzIEdST1VQIEJZIG1jYyBPUkRFUiBCWSBjb3VudCgpIERFU0M=). Although you cannot create temporary tables there. From 6f8f7150ffc2e70cb979659a0c6524d648101532 Mon Sep 17 00:00:00 2001 From: Yatsishin Ilya <2159081+qoega@users.noreply.github.com> Date: Fri, 21 Oct 2022 16:42:45 +0000 Subject: [PATCH 080/266] style --- tests/ci/sqlancer_check.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/tests/ci/sqlancer_check.py b/tests/ci/sqlancer_check.py index 2f320fbbf71..f41ae14b2d3 100644 --- a/tests/ci/sqlancer_check.py +++ b/tests/ci/sqlancer_check.py @@ -21,6 +21,7 @@ from build_download_helper import get_build_name_for_check, read_build_urls from docker_pull_helper import get_image_with_version from commit_status_helper import post_commit_status from clickhouse_helper import ClickHouseHelper, prepare_tests_results_for_clickhouse +from upload_result_helper import upload_results from stopwatch import Stopwatch from rerun_helper import RerunHelper @@ -141,6 +142,8 @@ if __name__ == "__main__": report_url = GITHUB_RUN_URL + status = "succeess" + test_results = [] # Try to get status message saved by the SQLancer try: with open( @@ -151,7 +154,6 @@ if __name__ == "__main__": with open( os.path.join(workspace_path, "summary.tsv"), "r", encoding="utf-8" ) as summary_f: - test_results = [] for line in summary_f: l = line.split("\t") test_results.append((l[0], l[1])) From db2f3baf39cc752dd2d80001091f86abae0de51e Mon Sep 17 00:00:00 2001 From: Yatsishin Ilya <2159081+qoega@users.noreply.github.com> Date: Fri, 21 Oct 2022 19:43:06 +0000 Subject: [PATCH 081/266] fix --- .github/workflows/master.yml | 1 + .github/workflows/pull_request.yml | 1 + tests/ci/sqlancer_check.py | 2 +- 3 files changed, 3 insertions(+), 1 deletion(-) diff --git a/.github/workflows/master.yml b/.github/workflows/master.yml index b373a3cc210..9de7891f1d7 100644 --- a/.github/workflows/master.yml +++ b/.github/workflows/master.yml @@ -3089,6 +3089,7 @@ jobs: - UnitTestsReleaseClang - SharedBuildSmokeTest - SQLancerTestRelease + - SQLancerTestDebug runs-on: [self-hosted, style-checker] steps: - name: Clear repository diff --git a/.github/workflows/pull_request.yml b/.github/workflows/pull_request.yml index 6335ccc9a35..29fff46c7ce 100644 --- a/.github/workflows/pull_request.yml +++ b/.github/workflows/pull_request.yml @@ -3612,6 +3612,7 @@ jobs: - CompatibilityCheck - IntegrationTestsFlakyCheck - SQLancerTestRelease + - SQLancerTestDebug runs-on: [self-hosted, style-checker] steps: - name: Clear repository diff --git a/tests/ci/sqlancer_check.py b/tests/ci/sqlancer_check.py index f41ae14b2d3..eced80db9b7 100644 --- a/tests/ci/sqlancer_check.py +++ b/tests/ci/sqlancer_check.py @@ -184,7 +184,7 @@ if __name__ == "__main__": prepared_events = prepare_tests_results_for_clickhouse( pr_info, - test_result, + test_results, status, stopwatch.duration_seconds, stopwatch.start_time_str, From 39c88c74e84b1109015ad5b80e164bf57799e3ba Mon Sep 17 00:00:00 2001 From: JackyWoo Date: Sat, 22 Oct 2022 22:31:17 +0800 Subject: [PATCH 082/266] check whether last manually created snapshot is done --- contrib/NuRaft | 2 +- docs/en/operations/clickhouse-keeper.md | 8 +++++++- src/Coordination/FourLetterCommand.cpp | 7 ++++++- src/Coordination/FourLetterCommand.h | 14 ++++++++++++++ src/Coordination/KeeperDispatcher.h | 6 ++++++ src/Coordination/KeeperServer.cpp | 16 +++++++++++++++- src/Coordination/KeeperServer.h | 6 ++++++ .../test_keeper_four_word_command/test.py | 9 ++++++--- 8 files changed, 61 insertions(+), 7 deletions(-) diff --git a/contrib/NuRaft b/contrib/NuRaft index 1be805e7cb2..e4e746a24eb 160000 --- a/contrib/NuRaft +++ b/contrib/NuRaft @@ -1 +1 @@ -Subproject commit 1be805e7cb2494aa8170015493474379b0362dfc +Subproject commit e4e746a24eb56861a86f3672771e3308d8c40722 diff --git a/docs/en/operations/clickhouse-keeper.md b/docs/en/operations/clickhouse-keeper.md index 03eddd4f6ed..66b4685bff5 100644 --- a/docs/en/operations/clickhouse-keeper.md +++ b/docs/en/operations/clickhouse-keeper.md @@ -309,12 +309,18 @@ Sessions with Ephemerals (1): /clickhouse/task_queue/ddl ``` -- `csnp`: Schedule a snapshot creation task. Return `"Snapshot creation scheduled."` if successfully scheduled or Fail to scheduled snapshot creation.` if failed. +- `csnp`: Schedule a snapshot creation task. Return `Snapshot creation scheduled.` if successfully scheduled or `Fail to scheduled snapshot creation.` if failed. ``` Snapshot creation scheduled. ``` +- `snpd`: Whether the last successfully scheduled snapshot creation is done. Return `Yes` if true or `No` if false. + +``` +Yes +``` + ## [experimental] Migration from ZooKeeper {#migration-from-zookeeper} Seamlessly migration from ZooKeeper to ClickHouse Keeper is impossible you have to stop your ZooKeeper cluster, convert data and start ClickHouse Keeper. `clickhouse-keeper-converter` tool allows converting ZooKeeper logs and snapshots to ClickHouse Keeper snapshot. It works only with ZooKeeper > 3.4. Steps for migration: diff --git a/src/Coordination/FourLetterCommand.cpp b/src/Coordination/FourLetterCommand.cpp index 70009703c5a..3d1077ea84c 100644 --- a/src/Coordination/FourLetterCommand.cpp +++ b/src/Coordination/FourLetterCommand.cpp @@ -477,7 +477,12 @@ String ApiVersionCommand::run() String CreateSnapshotCommand::run() { - return keeper_dispatcher.createSnapshot() ? "Snapshot creation scheduled." : "Fail to scheduled snapshot creation."; + return keeper_dispatcher.createSnapshot() ? "Snapshot creation scheduled." : "Fail to scheduled snapshot creation task."; +} + +String CheckSnapshotDoneCommand::run() +{ + return keeper_dispatcher.snapshotDone() ? "Snapshot creation done." : "Fail to scheduled snapshot creation task."; } } diff --git a/src/Coordination/FourLetterCommand.h b/src/Coordination/FourLetterCommand.h index 5001a750d66..28f1d7f153f 100644 --- a/src/Coordination/FourLetterCommand.h +++ b/src/Coordination/FourLetterCommand.h @@ -340,4 +340,18 @@ struct CreateSnapshotCommand : public IFourLetterCommand String run() override; ~CreateSnapshotCommand() override = default; }; + +/// Check whether last manual snapshot done +struct CheckSnapshotDoneCommand : public IFourLetterCommand +{ + explicit CheckSnapshotDoneCommand(KeeperDispatcher & keeper_dispatcher_) + : IFourLetterCommand(keeper_dispatcher_) + { + } + + String name() override { return "snpd"; } + String run() override; + ~CheckSnapshotDoneCommand() override = default; +}; + } diff --git a/src/Coordination/KeeperDispatcher.h b/src/Coordination/KeeperDispatcher.h index 79212ea3040..48681957c13 100644 --- a/src/Coordination/KeeperDispatcher.h +++ b/src/Coordination/KeeperDispatcher.h @@ -209,6 +209,12 @@ public: { return server->createSnapshot(); } + + /// Whether the last manually created snapshot is done + bool snapshotDone() + { + return server->snapshotDone(); + } }; } diff --git a/src/Coordination/KeeperServer.cpp b/src/Coordination/KeeperServer.cpp index e0186927b54..87ebea0b4ab 100644 --- a/src/Coordination/KeeperServer.cpp +++ b/src/Coordination/KeeperServer.cpp @@ -114,6 +114,7 @@ KeeperServer::KeeperServer( , is_recovering(config.getBool("keeper_server.force_recovery", false)) , keeper_context{std::make_shared()} , create_snapshot_on_exit(config.getBool("keeper_server.create_snapshot_on_exit", true)) + , last_manual_snapshot_log_idx(0) { if (coordination_settings->quorum_reads) LOG_WARNING(log, "Quorum reads enabled, Keeper will work slower."); @@ -908,7 +909,20 @@ Keeper4LWInfo KeeperServer::getPartiallyFilled4LWInfo() const bool KeeperServer::createSnapshot() { - return raft_instance->create_snapshot(); + std::lock_guard lock(snapshot_mutex); + if (raft_instance->create_snapshot()) + { + last_manual_snapshot_log_idx = raft_instance->get_last_snapshot_idx(); + LOG_INFO(log, "Successfully schedule a keeper snapshot creation task at log index {}", last_manual_snapshot_log_idx); + return true; + } + return false; +} + +bool KeeperServer::snapshotDone() +{ + std::lock_guard lock(snapshot_mutex); + return last_manual_snapshot_log_idx != 0 && last_manual_snapshot_log_idx == raft_instance->get_last_snapshot_idx(); } } diff --git a/src/Coordination/KeeperServer.h b/src/Coordination/KeeperServer.h index ec832199387..11e3b75d127 100644 --- a/src/Coordination/KeeperServer.h +++ b/src/Coordination/KeeperServer.h @@ -66,6 +66,10 @@ private: const bool create_snapshot_on_exit; + /// Used to check whether the previous manually created snapshot complete. + uint64_t last_manual_snapshot_log_idx; + std::mutex snapshot_mutex; + public: KeeperServer( const KeeperConfigurationAndSettingsPtr & settings_, @@ -133,6 +137,8 @@ public: bool waitConfigurationUpdate(const ConfigUpdateAction & task); bool createSnapshot(); + + bool snapshotDone(); }; } diff --git a/tests/integration/test_keeper_four_word_command/test.py b/tests/integration/test_keeper_four_word_command/test.py index 2b2343757bb..bfe0b2a96e4 100644 --- a/tests/integration/test_keeper_four_word_command/test.py +++ b/tests/integration/test_keeper_four_word_command/test.py @@ -598,7 +598,7 @@ def test_cmd_wchp(started_cluster): destroy_zk_client(zk) -def test_cmd_csnp(started_cluster): +def test_cmd_snapshot(started_cluster): zk = None try: wait_nodes() @@ -607,7 +607,10 @@ def test_cmd_csnp(started_cluster): zk = get_fake_zk(node1.name, timeout=30.0) - data = send_4lw_cmd(cmd="csnp") - assert data == "Snapshot creation scheduled." + create = send_4lw_cmd(cmd="csnp") + assert create == "Snapshot creation scheduled." + + check = send_4lw_cmd(cmd="snpd") + assert (check == "Yes" or check == "No") finally: destroy_zk_client(zk) From 42e391a0191ef046a499fab5c245e5714475cbe7 Mon Sep 17 00:00:00 2001 From: JackyWoo Date: Sat, 22 Oct 2022 22:47:03 +0800 Subject: [PATCH 083/266] fix test --- tests/integration/test_keeper_four_word_command/test.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/integration/test_keeper_four_word_command/test.py b/tests/integration/test_keeper_four_word_command/test.py index bfe0b2a96e4..4949d6f70de 100644 --- a/tests/integration/test_keeper_four_word_command/test.py +++ b/tests/integration/test_keeper_four_word_command/test.py @@ -611,6 +611,6 @@ def test_cmd_snapshot(started_cluster): assert create == "Snapshot creation scheduled." check = send_4lw_cmd(cmd="snpd") - assert (check == "Yes" or check == "No") + assert check == "Yes" or check == "No" finally: destroy_zk_client(zk) From 409fb28d0cd9cfcad87d8eb1e073d77f892260bf Mon Sep 17 00:00:00 2001 From: Alexey Milovidov Date: Sun, 23 Oct 2022 01:52:30 +0200 Subject: [PATCH 084/266] Fix build --- src/Common/ProgressIndication.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Common/ProgressIndication.cpp b/src/Common/ProgressIndication.cpp index f1abd20a6c8..b049edcdcf7 100644 --- a/src/Common/ProgressIndication.cpp +++ b/src/Common/ProgressIndication.cpp @@ -152,7 +152,7 @@ void ProgressIndication::writeProgress(WriteBufferFromFileDescriptor & message) const char * indicator = indicators[increment % 8]; - size_t terminal_width = getTerminalWidth(message.getFD()); + size_t terminal_width = getTerminalWidth(); if (!written_progress_chars) { From c386898268f580336aa6710e91dc43cbf5607383 Mon Sep 17 00:00:00 2001 From: Alexey Milovidov Date: Sun, 23 Oct 2022 04:46:07 +0200 Subject: [PATCH 085/266] Fix error --- src/Client/ClientBase.cpp | 21 +++++++++++---------- 1 file changed, 11 insertions(+), 10 deletions(-) diff --git a/src/Client/ClientBase.cpp b/src/Client/ClientBase.cpp index fd8aec6973f..1c0251e7671 100644 --- a/src/Client/ClientBase.cpp +++ b/src/Client/ClientBase.cpp @@ -415,7 +415,7 @@ void ClientBase::onData(Block & block, ASTPtr parsed_query) return; /// If results are written INTO OUTFILE, we can avoid clearing progress to avoid flicker. - if (need_render_progress && (stdout_is_a_tty || is_interactive) && (!select_into_file || select_into_file_and_stdout)) + if (need_render_progress && tty_buf && (!select_into_file || select_into_file_and_stdout)) progress_indication.clearProgressOutput(*tty_buf); try @@ -433,7 +433,7 @@ void ClientBase::onData(Block & block, ASTPtr parsed_query) output_format->flush(); /// Restore progress bar after data block. - if (need_render_progress && (stdout_is_a_tty || is_interactive)) + if (need_render_progress && tty_buf) { if (select_into_file && !select_into_file_and_stdout) std::cerr << "\r"; @@ -445,7 +445,7 @@ void ClientBase::onData(Block & block, ASTPtr parsed_query) void ClientBase::onLogData(Block & block) { initLogsOutputStream(); - if (need_render_progress) + if (need_render_progress && tty_buf) progress_indication.clearProgressOutput(*tty_buf); logs_out_stream->writeLogs(block); logs_out_stream->flush(); @@ -989,14 +989,14 @@ void ClientBase::onProgress(const Progress & value) if (output_format) output_format->onProgress(value); - if (need_render_progress) + if (need_render_progress && tty_buf) progress_indication.writeProgress(*tty_buf); } void ClientBase::onEndOfStream() { - if (need_render_progress) + if (need_render_progress && tty_buf) progress_indication.clearProgressOutput(*tty_buf); if (output_format) @@ -1048,7 +1048,7 @@ void ClientBase::onProfileEvents(Block & block) } progress_indication.updateThreadEventData(thread_times); - if (need_render_progress) + if (need_render_progress && tty_buf) progress_indication.writeProgress(*tty_buf); if (profile_events.print) @@ -1056,7 +1056,7 @@ void ClientBase::onProfileEvents(Block & block) if (profile_events.watch.elapsedMilliseconds() >= profile_events.delay_ms) { initLogsOutputStream(); - if (need_render_progress) + if (need_render_progress && tty_buf) progress_indication.clearProgressOutput(*tty_buf); logs_out_stream->writeProfileEvents(block); logs_out_stream->flush(); @@ -1231,7 +1231,8 @@ void ClientBase::sendData(Block & sample, const ColumnsDescription & columns_des progress_indication.updateProgress(Progress(file_progress)); /// Set callback to be called on file progress. - progress_indication.setFileProgressCallback(global_context, *tty_buf); + if (tty_buf) + progress_indication.setFileProgressCallback(global_context, *tty_buf); } /// If data fetched from file (maybe compressed file) @@ -1483,7 +1484,7 @@ bool ClientBase::receiveEndOfQuery() void ClientBase::cancelQuery() { connection->sendCancel(); - if (need_render_progress) + if (need_render_progress && tty_buf) progress_indication.clearProgressOutput(*tty_buf); std::cout << "Cancelling query." << std::endl; @@ -1606,7 +1607,7 @@ void ClientBase::processParsedSingleQuery(const String & full_query, const Strin if (profile_events.last_block) { initLogsOutputStream(); - if (need_render_progress) + if (need_render_progress && tty_buf) progress_indication.clearProgressOutput(*tty_buf); logs_out_stream->writeProfileEvents(profile_events.last_block); logs_out_stream->flush(); From 8eb751fbe551aeb8a5af94c0c7e7b4960865645a Mon Sep 17 00:00:00 2001 From: Alexey Milovidov Date: Sun, 23 Oct 2022 05:28:28 +0200 Subject: [PATCH 086/266] Fix error --- src/Client/ClientBase.cpp | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/Client/ClientBase.cpp b/src/Client/ClientBase.cpp index 1c0251e7671..ba553b07e73 100644 --- a/src/Client/ClientBase.cpp +++ b/src/Client/ClientBase.cpp @@ -1487,7 +1487,9 @@ void ClientBase::cancelQuery() if (need_render_progress && tty_buf) progress_indication.clearProgressOutput(*tty_buf); - std::cout << "Cancelling query." << std::endl; + if (is_interactive) + std::cout << "Cancelling query." << std::endl; + cancelled = true; } From 6242e93c81c22a1306c4dd35c263f65e22048fe9 Mon Sep 17 00:00:00 2001 From: Nikolay Degterinsky Date: Mon, 24 Oct 2022 02:11:08 +0000 Subject: [PATCH 087/266] Fixes & better tests --- src/Functions/vectorFunctions.cpp | 8 ++++---- .../0_stateless/02457_tuple_of_intervals.reference | 5 +++++ tests/queries/0_stateless/02457_tuple_of_intervals.sql | 7 +++++++ 3 files changed, 16 insertions(+), 4 deletions(-) diff --git a/src/Functions/vectorFunctions.cpp b/src/Functions/vectorFunctions.cpp index 78f35342d60..20835f59cc1 100644 --- a/src/Functions/vectorFunctions.cpp +++ b/src/Functions/vectorFunctions.cpp @@ -434,7 +434,7 @@ public: DataTypePtr getReturnTypeImpl(const ColumnsWithTypeAndName & arguments) const override { - if (!isDate(arguments[0].type) && !isDate32(arguments[0].type) && !isDateTime(arguments[0].type) && !isDateTime64(arguments[0].type)) + if (!isDateOrDate32(arguments[0].type) && !isDateTime(arguments[0].type) && !isDateTime64(arguments[0].type)) throw Exception{ErrorCodes::ILLEGAL_TYPE_OF_ARGUMENT, "Illegal type {} of first argument of function {}. Should be a date or a date with time", arguments[0].type->getName(), getName()}; @@ -545,7 +545,7 @@ public: if (!isInterval(arguments[1])) throw Exception(ErrorCodes::ILLEGAL_TYPE_OF_ARGUMENT, "Illegal type {} of second argument of function {}, must be Interval", - arguments[0]->getName(), getName()); + arguments[1]->getName(), getName()); DataTypes types; @@ -559,7 +559,7 @@ public: if (!isInterval(type)) throw Exception(ErrorCodes::ILLEGAL_TYPE_OF_ARGUMENT, "Illegal type {} of Tuple element of first argument of function {}, must be Interval", - types.back()->getName(), getName()); + type->getName(), getName()); types = cur_types; } @@ -582,7 +582,7 @@ public: if (!isInterval(arguments[1].type)) throw Exception(ErrorCodes::ILLEGAL_TYPE_OF_ARGUMENT, "Illegal type {} of second argument of function {}, must be Interval", - arguments[0].type->getName(), getName()); + arguments[1].type->getName(), getName()); Columns tuple_columns; diff --git a/tests/queries/0_stateless/02457_tuple_of_intervals.reference b/tests/queries/0_stateless/02457_tuple_of_intervals.reference index d5ffbc33dc0..e635aec1163 100644 --- a/tests/queries/0_stateless/02457_tuple_of_intervals.reference +++ b/tests/queries/0_stateless/02457_tuple_of_intervals.reference @@ -5,6 +5,11 @@ SELECT (toIntervalSecond(-1), toIntervalMinute(2), toIntervalMonth(-3), toInterv 2022-10-10 (2) (0) +2022-11-12 +2022-09-10 +(1,2) +(1,0) +--- 2022-10-12 2022-10-10 2022-10-12 diff --git a/tests/queries/0_stateless/02457_tuple_of_intervals.sql b/tests/queries/0_stateless/02457_tuple_of_intervals.sql index 494914d4d4f..be9ccb50d92 100644 --- a/tests/queries/0_stateless/02457_tuple_of_intervals.sql +++ b/tests/queries/0_stateless/02457_tuple_of_intervals.sql @@ -8,6 +8,13 @@ SELECT subtractTupleOfIntervals('2022-10-11'::Date, tuple(INTERVAL 1 DAY)); SELECT addInterval(tuple(INTERVAL 1 SECOND), INTERVAL 1 SECOND); SELECT subtractInterval(tuple(INTERVAL 1 SECOND), INTERVAL 1 SECOND); +SELECT addTupleOfIntervals('2022-10-11'::Date, (INTERVAL 1 DAY, INTERVAL 1 MONTH)); +SELECT subtractTupleOfIntervals('2022-10-11'::Date, (INTERVAL 1 DAY, INTERVAL 1 MONTH)); +SELECT addInterval((INTERVAL 1 DAY, INTERVAL 1 SECOND), INTERVAL 1 SECOND); +SELECT subtractInterval(tuple(INTERVAL 1 DAY, INTERVAL 1 SECOND), INTERVAL 1 SECOND); + +SELECT '---'; + SELECT '2022-10-11'::Date + tuple(INTERVAL 1 DAY); SELECT '2022-10-11'::Date - tuple(INTERVAL 1 DAY); SELECT tuple(INTERVAL 1 DAY) + '2022-10-11'::Date; From 61563d4f19b2d358c94e4044f48267ab94160574 Mon Sep 17 00:00:00 2001 From: xiedeyantu Date: Mon, 24 Oct 2022 11:52:20 +0800 Subject: [PATCH 088/266] better --- .../System/StorageSystemDetachedParts.cpp | 23 +++++++++++-------- 1 file changed, 13 insertions(+), 10 deletions(-) diff --git a/src/Storages/System/StorageSystemDetachedParts.cpp b/src/Storages/System/StorageSystemDetachedParts.cpp index 980529032c4..11c4f7843ac 100644 --- a/src/Storages/System/StorageSystemDetachedParts.cpp +++ b/src/Storages/System/StorageSystemDetachedParts.cpp @@ -44,19 +44,22 @@ static void calculateTotalSizeOnDiskImpl(const DiskPtr & disk, const String & fr { tryLogCurrentException(__PRETTY_FUNCTION__); } - return; } - std::vector files; - try + else { - disk->listFiles(from, files); + DirectoryIteratorPtr it; + try + { + it = disk->iterateDirectory(from); + } + catch (...) + { + tryLogCurrentException(__PRETTY_FUNCTION__); + } + + for (; it->isValid(); it->next()) + calculateTotalSizeOnDiskImpl(disk, fs::path(from) / it->name(), total_size); } - catch (...) - { - tryLogCurrentException(__PRETTY_FUNCTION__); - } - for (const auto & file : files) - calculateTotalSizeOnDiskImpl(disk, fs::path(from) / file, total_size); } static UInt64 calculateTotalSizeOnDisk(const DiskPtr & disk, const String & from) From c8a4dc088f3842dd886475954b832a7b709e1439 Mon Sep 17 00:00:00 2001 From: Yatsishin Ilya <2159081+qoega@users.noreply.github.com> Date: Mon, 24 Oct 2022 08:25:21 +0000 Subject: [PATCH 089/266] fix workflows --- .github/workflows/master.yml | 34 ++++++++++++++++++++++++++++++ .github/workflows/pull_request.yml | 34 ++++++++++++++++++++++++++++++ 2 files changed, 68 insertions(+) diff --git a/.github/workflows/master.yml b/.github/workflows/master.yml index 9de7891f1d7..f80efd98f54 100644 --- a/.github/workflows/master.yml +++ b/.github/workflows/master.yml @@ -3029,6 +3029,40 @@ jobs: docker ps --quiet | xargs --no-run-if-empty docker kill ||: docker ps --all --quiet | xargs --no-run-if-empty docker rm -f ||: sudo rm -fr "$TEMP_PATH" + SQLancerTestDebug: + needs: [BuilderDebDebug] + runs-on: [self-hosted, fuzzer-unit-tester] + steps: + - name: Set envs + run: | + cat >> "$GITHUB_ENV" << 'EOF' + TEMP_PATH=${{runner.temp}}/sqlancer_debug + REPORTS_PATH=${{runner.temp}}/reports_dir + CHECK_NAME=SQLancer (release) + REPO_COPY=${{runner.temp}}/sqlancer_debug/ClickHouse + EOF + - name: Download json reports + uses: actions/download-artifact@v2 + with: + path: ${{ env.REPORTS_PATH }} + - name: Clear repository + run: | + sudo rm -fr "$GITHUB_WORKSPACE" && mkdir "$GITHUB_WORKSPACE" + - name: Check out repository code + uses: actions/checkout@v2 + - name: SQLancer + run: | + sudo rm -fr "$TEMP_PATH" + mkdir -p "$TEMP_PATH" + cp -r "$GITHUB_WORKSPACE" "$TEMP_PATH" + cd "$REPO_COPY/tests/ci" + python3 sqlancer_check.py "$CHECK_NAME" + - name: Cleanup + if: always() + run: | + docker ps --quiet | xargs --no-run-if-empty docker kill ||: + docker ps --all --quiet | xargs --no-run-if-empty docker rm -f ||: + sudo rm -fr "$TEMP_PATH" FinishCheck: needs: - DockerHubPush diff --git a/.github/workflows/pull_request.yml b/.github/workflows/pull_request.yml index 29fff46c7ce..cf6e32e136f 100644 --- a/.github/workflows/pull_request.yml +++ b/.github/workflows/pull_request.yml @@ -3527,6 +3527,40 @@ jobs: docker ps --quiet | xargs --no-run-if-empty docker kill ||: docker ps --all --quiet | xargs --no-run-if-empty docker rm -f ||: sudo rm -fr "$TEMP_PATH" + SQLancerTestDebug: + needs: [BuilderDebDebug] + runs-on: [self-hosted, fuzzer-unit-tester] + steps: + - name: Set envs + run: | + cat >> "$GITHUB_ENV" << 'EOF' + TEMP_PATH=${{runner.temp}}/sqlancer_debug + REPORTS_PATH=${{runner.temp}}/reports_dir + CHECK_NAME=SQLancer (release) + REPO_COPY=${{runner.temp}}/sqlancer_debug/ClickHouse + EOF + - name: Download json reports + uses: actions/download-artifact@v2 + with: + path: ${{ env.REPORTS_PATH }} + - name: Clear repository + run: | + sudo rm -fr "$GITHUB_WORKSPACE" && mkdir "$GITHUB_WORKSPACE" + - name: Check out repository code + uses: actions/checkout@v2 + - name: SQLancer + run: | + sudo rm -fr "$TEMP_PATH" + mkdir -p "$TEMP_PATH" + cp -r "$GITHUB_WORKSPACE" "$TEMP_PATH" + cd "$REPO_COPY/tests/ci" + python3 sqlancer_check.py "$CHECK_NAME" + - name: Cleanup + if: always() + run: | + docker ps --quiet | xargs --no-run-if-empty docker kill ||: + docker ps --all --quiet | xargs --no-run-if-empty docker rm -f ||: + sudo rm -fr "$TEMP_PATH" ############################################################################################# ###################################### JEPSEN TESTS ######################################### ############################################################################################# From 2f30c817bfb51ae47bb7dde8c15f3f4999d0d924 Mon Sep 17 00:00:00 2001 From: JackyWoo Date: Mon, 24 Oct 2022 17:23:47 +0800 Subject: [PATCH 090/266] little fix --- src/Coordination/KeeperServer.cpp | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/Coordination/KeeperServer.cpp b/src/Coordination/KeeperServer.cpp index 87ebea0b4ab..042ab35d709 100644 --- a/src/Coordination/KeeperServer.cpp +++ b/src/Coordination/KeeperServer.cpp @@ -910,10 +910,11 @@ Keeper4LWInfo KeeperServer::getPartiallyFilled4LWInfo() const bool KeeperServer::createSnapshot() { std::lock_guard lock(snapshot_mutex); - if (raft_instance->create_snapshot()) + uint64_t log_idx = raft_instance->create_snapshot(); + if (log_idx != 0) { - last_manual_snapshot_log_idx = raft_instance->get_last_snapshot_idx(); - LOG_INFO(log, "Successfully schedule a keeper snapshot creation task at log index {}", last_manual_snapshot_log_idx); + last_manual_snapshot_log_idx = log_idx; + LOG_INFO(log, "Successfully schedule a keeper snapshot creation task at log index {}", log_idx); return true; } return false; From 8da389faf41dad9630decc6de0ca56e036bb50bb Mon Sep 17 00:00:00 2001 From: Yatsishin Ilya <2159081+qoega@users.noreply.github.com> Date: Mon, 24 Oct 2022 11:28:43 +0000 Subject: [PATCH 091/266] fix name --- .github/workflows/master.yml | 2 +- .github/workflows/pull_request.yml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/master.yml b/.github/workflows/master.yml index f80efd98f54..33d4cb4f2cc 100644 --- a/.github/workflows/master.yml +++ b/.github/workflows/master.yml @@ -3038,7 +3038,7 @@ jobs: cat >> "$GITHUB_ENV" << 'EOF' TEMP_PATH=${{runner.temp}}/sqlancer_debug REPORTS_PATH=${{runner.temp}}/reports_dir - CHECK_NAME=SQLancer (release) + CHECK_NAME=SQLancer (debug) REPO_COPY=${{runner.temp}}/sqlancer_debug/ClickHouse EOF - name: Download json reports diff --git a/.github/workflows/pull_request.yml b/.github/workflows/pull_request.yml index cf6e32e136f..210dc1dce4d 100644 --- a/.github/workflows/pull_request.yml +++ b/.github/workflows/pull_request.yml @@ -3536,7 +3536,7 @@ jobs: cat >> "$GITHUB_ENV" << 'EOF' TEMP_PATH=${{runner.temp}}/sqlancer_debug REPORTS_PATH=${{runner.temp}}/reports_dir - CHECK_NAME=SQLancer (release) + CHECK_NAME=SQLancer (debug) REPO_COPY=${{runner.temp}}/sqlancer_debug/ClickHouse EOF - name: Download json reports From b5d1c4e6574fbc4916056a3c78ea87aa7c74a3f6 Mon Sep 17 00:00:00 2001 From: JackyWoo Date: Mon, 24 Oct 2022 20:08:58 +0800 Subject: [PATCH 092/266] replace snpd with lgif --- docs/en/operations/clickhouse-keeper.md | 13 +++++++++---- src/Coordination/FourLetterCommand.cpp | 19 ++++++++++++++++--- src/Coordination/FourLetterCommand.h | 17 ++++++++++++----- src/Coordination/Keeper4LWInfo.h | 22 ++++++++++++++++++++++ src/Coordination/KeeperDispatcher.h | 8 ++++---- src/Coordination/KeeperServer.cpp | 24 +++++++++++++----------- src/Coordination/KeeperServer.h | 8 ++------ 7 files changed, 78 insertions(+), 33 deletions(-) diff --git a/docs/en/operations/clickhouse-keeper.md b/docs/en/operations/clickhouse-keeper.md index 66b4685bff5..2ab76e1a1ea 100644 --- a/docs/en/operations/clickhouse-keeper.md +++ b/docs/en/operations/clickhouse-keeper.md @@ -309,16 +309,21 @@ Sessions with Ephemerals (1): /clickhouse/task_queue/ddl ``` -- `csnp`: Schedule a snapshot creation task. Return `Snapshot creation scheduled.` if successfully scheduled or `Fail to scheduled snapshot creation.` if failed. +- `csnp`: Schedule a snapshot creation task. Return `Snapshot creation scheduled with last committed log index xxx.` if successfully scheduled or `Fail to scheduled snapshot creation task.` if failed. ``` -Snapshot creation scheduled. +Snapshot creation scheduled with last committed log index 100. ``` -- `snpd`: Whether the last successfully scheduled snapshot creation is done. Return `Yes` if true or `No` if false. +- `lgif`: Keeper log information. `last_log_idx` : my last log index in log store; `last_log_term` : my last log term; `last_committed_log_idx` : my last committed log index in state machine; `leader_committed_log_idx` : leader's committed log index from my perspective; `target_committed_log_idx` : target log index should be committed to; `last_snapshot_idx` : the largest committed log index in last snapshot. ``` -Yes +last_log_idx : 101 +last_log_term : 1 +last_committed_log_idx : 100 +leader_committed_log_idx : 101 +target_committed_log_idx : 101 +last_snapshot_idx : 50 ``` ## [experimental] Migration from ZooKeeper {#migration-from-zookeeper} diff --git a/src/Coordination/FourLetterCommand.cpp b/src/Coordination/FourLetterCommand.cpp index 3d1077ea84c..c5841ce3404 100644 --- a/src/Coordination/FourLetterCommand.cpp +++ b/src/Coordination/FourLetterCommand.cpp @@ -139,6 +139,9 @@ void FourLetterCommandFactory::registerCommands(KeeperDispatcher & keeper_dispat FourLetterCommandPtr create_snapshot_command = std::make_shared(keeper_dispatcher); factory.registerCommand(create_snapshot_command); + FourLetterCommandPtr log_info_command = std::make_shared(keeper_dispatcher); + factory.registerCommand(log_info_command); + factory.initializeAllowList(keeper_dispatcher); factory.setInitialize(true); } @@ -477,12 +480,22 @@ String ApiVersionCommand::run() String CreateSnapshotCommand::run() { - return keeper_dispatcher.createSnapshot() ? "Snapshot creation scheduled." : "Fail to scheduled snapshot creation task."; + auto log_index = keeper_dispatcher.createSnapshot(); + return log_index > 0 ? "Snapshot creation scheduled with last committed log index " + std::to_string(log_index) + "." + : "Fail to scheduled snapshot creation task."; } -String CheckSnapshotDoneCommand::run() +String LogInfoCommand::run() { - return keeper_dispatcher.snapshotDone() ? "Snapshot creation done." : "Fail to scheduled snapshot creation task."; + KeeperLogInfo log_info = keeper_dispatcher.getKeeperLogInfo(); + StringBuffer ret; + print(ret, "last_log_idx", log_info.last_log_idx); + print(ret, "last_log_term", log_info.last_log_term); + print(ret, "last_committed_log_idx", log_info.last_committed_log_idx); + print(ret, "leader_committed_log_idx", log_info.leader_committed_log_idx); + print(ret, "target_committed_log_idx", log_info.target_committed_log_idx); + print(ret, "last_snapshot_idx", log_info.last_snapshot_idx); + return ret.str(); } } diff --git a/src/Coordination/FourLetterCommand.h b/src/Coordination/FourLetterCommand.h index 28f1d7f153f..99005bab987 100644 --- a/src/Coordination/FourLetterCommand.h +++ b/src/Coordination/FourLetterCommand.h @@ -341,17 +341,24 @@ struct CreateSnapshotCommand : public IFourLetterCommand ~CreateSnapshotCommand() override = default; }; -/// Check whether last manual snapshot done -struct CheckSnapshotDoneCommand : public IFourLetterCommand +/** Raft log information: + * last_log_idx : 101 + * last_log_term : 1 + * last_committed_idx : 100 + * leader_committed_log_idx : 101 + * target_committed_log_idx : 101 + * last_snapshot_idx : 50 + */ +struct LogInfoCommand : public IFourLetterCommand { - explicit CheckSnapshotDoneCommand(KeeperDispatcher & keeper_dispatcher_) + explicit LogInfoCommand(KeeperDispatcher & keeper_dispatcher_) : IFourLetterCommand(keeper_dispatcher_) { } - String name() override { return "snpd"; } + String name() override { return "lgif"; } String run() override; - ~CheckSnapshotDoneCommand() override = default; + ~LogInfoCommand() override = default; }; } diff --git a/src/Coordination/Keeper4LWInfo.h b/src/Coordination/Keeper4LWInfo.h index 7d90152611e..dbddadaefbf 100644 --- a/src/Coordination/Keeper4LWInfo.h +++ b/src/Coordination/Keeper4LWInfo.h @@ -47,4 +47,26 @@ struct Keeper4LWInfo } }; +/// Keeper log information for 4lw commands +struct KeeperLogInfo +{ + /// My last log index in log store. + uint64_t last_log_idx; + + /// My last log term. + uint64_t last_log_term; + + /// My last committed log index in state machine. + uint64_t last_committed_log_idx; + + /// Leader's committed log index from my perspective. + uint64_t leader_committed_log_idx; + + /// Target log index should be committed to. + uint64_t target_committed_log_idx; + + /// The largest committed log index in last snapshot. + uint64_t last_snapshot_idx; +}; + } diff --git a/src/Coordination/KeeperDispatcher.h b/src/Coordination/KeeperDispatcher.h index 48681957c13..0126bf8a1e5 100644 --- a/src/Coordination/KeeperDispatcher.h +++ b/src/Coordination/KeeperDispatcher.h @@ -204,16 +204,16 @@ public: keeper_stats.reset(); } - /// Create snapshot manually - bool createSnapshot() + /// Create snapshot manually, return the last committed log index in the snapshot + uint64_t createSnapshot() { return server->createSnapshot(); } /// Whether the last manually created snapshot is done - bool snapshotDone() + KeeperLogInfo getKeeperLogInfo() { - return server->snapshotDone(); + return server->getKeeperLogInfo(); } }; diff --git a/src/Coordination/KeeperServer.cpp b/src/Coordination/KeeperServer.cpp index 042ab35d709..38070938fc5 100644 --- a/src/Coordination/KeeperServer.cpp +++ b/src/Coordination/KeeperServer.cpp @@ -907,23 +907,25 @@ Keeper4LWInfo KeeperServer::getPartiallyFilled4LWInfo() const return result; } -bool KeeperServer::createSnapshot() +uint64_t KeeperServer::createSnapshot() { - std::lock_guard lock(snapshot_mutex); uint64_t log_idx = raft_instance->create_snapshot(); if (log_idx != 0) - { - last_manual_snapshot_log_idx = log_idx; - LOG_INFO(log, "Successfully schedule a keeper snapshot creation task at log index {}", log_idx); - return true; - } - return false; + LOG_INFO(log, "Snapshot creation scheduled with last committed log index {}.", log_idx); + else + LOG_WARNING(log, "Fail to scheduled snapshot creation task."); + return log_idx; } -bool KeeperServer::snapshotDone() +KeeperLogInfo KeeperServer::getKeeperLogInfo() { - std::lock_guard lock(snapshot_mutex); - return last_manual_snapshot_log_idx != 0 && last_manual_snapshot_log_idx == raft_instance->get_last_snapshot_idx(); + KeeperLogInfo log_info; + log_info.last_log_idx = raft_instance->get_last_log_idx(); + log_info.last_log_term = raft_instance->get_last_log_term(); + log_info.leader_committed_log_idx = raft_instance->get_leader_committed_log_idx(); + log_info.target_committed_log_idx = raft_instance->get_target_committed_log_idx(); + log_info.last_snapshot_idx = raft_instance->get_last_snapshot_idx(); + return log_info; } } diff --git a/src/Coordination/KeeperServer.h b/src/Coordination/KeeperServer.h index 11e3b75d127..192c8f470b1 100644 --- a/src/Coordination/KeeperServer.h +++ b/src/Coordination/KeeperServer.h @@ -66,10 +66,6 @@ private: const bool create_snapshot_on_exit; - /// Used to check whether the previous manually created snapshot complete. - uint64_t last_manual_snapshot_log_idx; - std::mutex snapshot_mutex; - public: KeeperServer( const KeeperConfigurationAndSettingsPtr & settings_, @@ -136,9 +132,9 @@ public: /// Return true if update was successfully received. bool waitConfigurationUpdate(const ConfigUpdateAction & task); - bool createSnapshot(); + uint64_t createSnapshot(); - bool snapshotDone(); + KeeperLogInfo getKeeperLogInfo(); }; } From 163001382b07844b36f559f2c4373bcdce6d6836 Mon Sep 17 00:00:00 2001 From: xiedeyantu Date: Tue, 25 Oct 2022 09:43:29 +0800 Subject: [PATCH 093/266] Fix no progress indication on INSERT FROM INFILE --- src/Client/ClientBase.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Client/ClientBase.cpp b/src/Client/ClientBase.cpp index 5d7de8ec799..9a5002b17af 100644 --- a/src/Client/ClientBase.cpp +++ b/src/Client/ClientBase.cpp @@ -1173,7 +1173,7 @@ void ClientBase::sendData(Block & sample, const ColumnsDescription & columns_des bool have_data_in_stdin = !is_interactive && !stdin_is_a_tty && !std_in.eof(); - if (need_render_progress && have_data_in_stdin) + if (need_render_progress) { /// Set total_bytes_to_read for current fd. FileProgress file_progress(0, std_in.getFileSize()); From 9a36a509fe1272b53698eb0707249e3593fc3088 Mon Sep 17 00:00:00 2001 From: JackyWoo Date: Tue, 25 Oct 2022 17:15:49 +0800 Subject: [PATCH 094/266] fix test --- docs/en/operations/clickhouse-keeper.md | 20 +++++----- src/Coordination/FourLetterCommand.cpp | 25 ++++++++---- src/Coordination/FourLetterCommand.h | 15 ++++--- src/Coordination/Keeper4LWInfo.h | 6 +++ src/Coordination/KeeperServer.cpp | 4 +- .../test_keeper_four_word_command/test.py | 39 ++++++++++++++++--- 6 files changed, 79 insertions(+), 30 deletions(-) diff --git a/docs/en/operations/clickhouse-keeper.md b/docs/en/operations/clickhouse-keeper.md index 2ab76e1a1ea..8eee97ed275 100644 --- a/docs/en/operations/clickhouse-keeper.md +++ b/docs/en/operations/clickhouse-keeper.md @@ -309,21 +309,23 @@ Sessions with Ephemerals (1): /clickhouse/task_queue/ddl ``` -- `csnp`: Schedule a snapshot creation task. Return `Snapshot creation scheduled with last committed log index xxx.` if successfully scheduled or `Fail to scheduled snapshot creation task.` if failed. +- `csnp`: Schedule a snapshot creation task. Return the last committed log index of the scheduled snapshot if successfully scheduled or `Fail to scheduled snapshot creation task.` if failed. ``` -Snapshot creation scheduled with last committed log index 100. +100 ``` -- `lgif`: Keeper log information. `last_log_idx` : my last log index in log store; `last_log_term` : my last log term; `last_committed_log_idx` : my last committed log index in state machine; `leader_committed_log_idx` : leader's committed log index from my perspective; `target_committed_log_idx` : target log index should be committed to; `last_snapshot_idx` : the largest committed log index in last snapshot. +- `lgif`: Keeper log information. `first_log_idx` : my first log index in log store; `first_log_term` : my first log term; `last_log_idx` : my last log index in log store; `last_log_term` : my last log term; `last_committed_log_idx` : my last committed log index in state machine; `leader_committed_log_idx` : leader's committed log index from my perspective; `target_committed_log_idx` : target log index should be committed to; `last_snapshot_idx` : the largest committed log index in last snapshot. ``` -last_log_idx : 101 -last_log_term : 1 -last_committed_log_idx : 100 -leader_committed_log_idx : 101 -target_committed_log_idx : 101 -last_snapshot_idx : 50 +first_log_idx 1 +first_log_term 1 +last_log_idx 101 +last_log_term 1 +last_committed_log_idx 100 +leader_committed_log_idx 101 +target_committed_log_idx 101 +last_snapshot_idx 50 ``` ## [experimental] Migration from ZooKeeper {#migration-from-zookeeper} diff --git a/src/Coordination/FourLetterCommand.cpp b/src/Coordination/FourLetterCommand.cpp index c5841ce3404..402270640d2 100644 --- a/src/Coordination/FourLetterCommand.cpp +++ b/src/Coordination/FourLetterCommand.cpp @@ -481,20 +481,29 @@ String ApiVersionCommand::run() String CreateSnapshotCommand::run() { auto log_index = keeper_dispatcher.createSnapshot(); - return log_index > 0 ? "Snapshot creation scheduled with last committed log index " + std::to_string(log_index) + "." - : "Fail to scheduled snapshot creation task."; + return log_index > 0 ? std::to_string(log_index) : "Fail to scheduled snapshot creation task."; } String LogInfoCommand::run() { KeeperLogInfo log_info = keeper_dispatcher.getKeeperLogInfo(); StringBuffer ret; - print(ret, "last_log_idx", log_info.last_log_idx); - print(ret, "last_log_term", log_info.last_log_term); - print(ret, "last_committed_log_idx", log_info.last_committed_log_idx); - print(ret, "leader_committed_log_idx", log_info.leader_committed_log_idx); - print(ret, "target_committed_log_idx", log_info.target_committed_log_idx); - print(ret, "last_snapshot_idx", log_info.last_snapshot_idx); + + auto append = [&ret] (String key, uint64_t value) -> void + { + writeText(key, ret); + writeText('\t', ret); + writeText(std::to_string(value), ret); + writeText('\n', ret); + }; + append("first_log_idx", log_info.first_log_idx); + append("first_log_term", log_info.first_log_idx); + append("last_log_idx", log_info.last_log_idx); + append("last_log_term", log_info.last_log_term); + append("last_committed_log_idx", log_info.last_committed_log_idx); + append("leader_committed_log_idx", log_info.leader_committed_log_idx); + append("target_committed_log_idx", log_info.target_committed_log_idx); + append("last_snapshot_idx", log_info.last_snapshot_idx); return ret.str(); } diff --git a/src/Coordination/FourLetterCommand.h b/src/Coordination/FourLetterCommand.h index 99005bab987..a8801474bb0 100644 --- a/src/Coordination/FourLetterCommand.h +++ b/src/Coordination/FourLetterCommand.h @@ -17,6 +17,7 @@ using FourLetterCommandPtr = std::shared_ptr; /// Just like zookeeper Four Letter Words commands, CH Keeper responds to a small set of commands. /// Each command is composed of four letters, these commands are useful to monitor and issue system problems. /// The feature is based on Zookeeper 3.5.9, details is in https://zookeeper.apache.org/doc/r3.5.9/zookeeperAdmin.html#sc_zkCommands. +/// Also we add some additional commands such as csnp, lgif etc. struct IFourLetterCommand { public: @@ -342,12 +343,14 @@ struct CreateSnapshotCommand : public IFourLetterCommand }; /** Raft log information: - * last_log_idx : 101 - * last_log_term : 1 - * last_committed_idx : 100 - * leader_committed_log_idx : 101 - * target_committed_log_idx : 101 - * last_snapshot_idx : 50 + * first_log_idx 1 + * first_log_term 1 + * last_log_idx 101 + * last_log_term 1 + * last_committed_idx 100 + * leader_committed_log_idx 101 + * target_committed_log_idx 101 + * last_snapshot_idx 50 */ struct LogInfoCommand : public IFourLetterCommand { diff --git a/src/Coordination/Keeper4LWInfo.h b/src/Coordination/Keeper4LWInfo.h index dbddadaefbf..105478457cc 100644 --- a/src/Coordination/Keeper4LWInfo.h +++ b/src/Coordination/Keeper4LWInfo.h @@ -50,6 +50,12 @@ struct Keeper4LWInfo /// Keeper log information for 4lw commands struct KeeperLogInfo { + /// My first log index in log store. + uint64_t first_log_idx; + + /// My first log term. + uint64_t first_log_term; + /// My last log index in log store. uint64_t last_log_idx; diff --git a/src/Coordination/KeeperServer.cpp b/src/Coordination/KeeperServer.cpp index 38070938fc5..bea69ea0ba8 100644 --- a/src/Coordination/KeeperServer.cpp +++ b/src/Coordination/KeeperServer.cpp @@ -114,7 +114,6 @@ KeeperServer::KeeperServer( , is_recovering(config.getBool("keeper_server.force_recovery", false)) , keeper_context{std::make_shared()} , create_snapshot_on_exit(config.getBool("keeper_server.create_snapshot_on_exit", true)) - , last_manual_snapshot_log_idx(0) { if (coordination_settings->quorum_reads) LOG_WARNING(log, "Quorum reads enabled, Keeper will work slower."); @@ -920,8 +919,11 @@ uint64_t KeeperServer::createSnapshot() KeeperLogInfo KeeperServer::getKeeperLogInfo() { KeeperLogInfo log_info; + log_info.first_log_idx = state_manager->load_log_store()->start_index(); + log_info.first_log_term = state_manager->load_log_store()->term_at(log_info.first_log_idx); log_info.last_log_idx = raft_instance->get_last_log_idx(); log_info.last_log_term = raft_instance->get_last_log_term(); + log_info.last_committed_log_idx = raft_instance->get_committed_log_idx(); log_info.leader_committed_log_idx = raft_instance->get_leader_committed_log_idx(); log_info.target_committed_log_idx = raft_instance->get_target_committed_log_idx(); log_info.last_snapshot_idx = raft_instance->get_last_snapshot_idx(); diff --git a/tests/integration/test_keeper_four_word_command/test.py b/tests/integration/test_keeper_four_word_command/test.py index 4949d6f70de..4559904f8b7 100644 --- a/tests/integration/test_keeper_four_word_command/test.py +++ b/tests/integration/test_keeper_four_word_command/test.py @@ -598,19 +598,46 @@ def test_cmd_wchp(started_cluster): destroy_zk_client(zk) -def test_cmd_snapshot(started_cluster): +def test_cmd_csnp(started_cluster): + zk = None + try: + wait_nodes() + zk = get_fake_zk(node1.name, timeout=30.0) + data = keeper_utils.send_4lw_cmd(cluster, node1, cmd="csnp") + try: + int(data) + assert True + except ValueError: + assert False + finally: + destroy_zk_client(zk) + + +def test_cmd_lgif(started_cluster): zk = None try: wait_nodes() clear_znodes() - reset_node_stats() zk = get_fake_zk(node1.name, timeout=30.0) + do_some_action(zk, create_cnt=100) - create = send_4lw_cmd(cmd="csnp") - assert create == "Snapshot creation scheduled." + data = keeper_utils.send_4lw_cmd(cluster, node1, cmd="lgif") + print(data) + reader = csv.reader(data.split("\n"), delimiter="\t") + result = {} - check = send_4lw_cmd(cmd="snpd") - assert check == "Yes" or check == "No" + for row in reader: + if len(row) != 0: + result[row[0]] = row[1] + + assert int(result["first_log_idx"]) == 1 + assert int(result["first_log_term"]) == 1 + assert int(result["last_log_idx"]) >= 1 + assert int(result["last_log_term"]) == 1 + assert int(result["last_committed_log_idx"]) >= 1 + assert int(result["leader_committed_log_idx"]) >= 1 + assert int(result["target_committed_log_idx"]) >= 1 + assert int(result["last_snapshot_idx"]) >= 1 finally: destroy_zk_client(zk) From c7a0ebeb05ba81f4259b2c5910ac88c10520cafc Mon Sep 17 00:00:00 2001 From: JackyWoo Date: Tue, 25 Oct 2022 17:46:24 +0800 Subject: [PATCH 095/266] little fix --- docs/en/operations/clickhouse-keeper.md | 2 +- src/Coordination/KeeperDispatcher.h | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/en/operations/clickhouse-keeper.md b/docs/en/operations/clickhouse-keeper.md index 8eee97ed275..269e18023df 100644 --- a/docs/en/operations/clickhouse-keeper.md +++ b/docs/en/operations/clickhouse-keeper.md @@ -309,7 +309,7 @@ Sessions with Ephemerals (1): /clickhouse/task_queue/ddl ``` -- `csnp`: Schedule a snapshot creation task. Return the last committed log index of the scheduled snapshot if successfully scheduled or `Fail to scheduled snapshot creation task.` if failed. +- `csnp`: Schedule a snapshot creation task. Return the last committed log index of the scheduled snapshot if success or `Fail to scheduled snapshot creation task.` if failed. ``` 100 diff --git a/src/Coordination/KeeperDispatcher.h b/src/Coordination/KeeperDispatcher.h index 0126bf8a1e5..84345ca1ff5 100644 --- a/src/Coordination/KeeperDispatcher.h +++ b/src/Coordination/KeeperDispatcher.h @@ -210,7 +210,7 @@ public: return server->createSnapshot(); } - /// Whether the last manually created snapshot is done + /// Get Raft information KeeperLogInfo getKeeperLogInfo() { return server->getKeeperLogInfo(); From c89c14a05d855c85d0ae318a6937dc33a6e9bae8 Mon Sep 17 00:00:00 2001 From: Yatsishin Ilya <2159081+qoega@users.noreply.github.com> Date: Tue, 25 Oct 2022 10:34:55 +0000 Subject: [PATCH 096/266] add NoREC --- .../test/sqlancer/process_sqlancer_result.py | 8 ++--- docker/test/sqlancer/run.sh | 13 ++++---- tests/ci/sqlancer_check.py | 30 ++++++++++--------- 3 files changed, 27 insertions(+), 24 deletions(-) diff --git a/docker/test/sqlancer/process_sqlancer_result.py b/docker/test/sqlancer/process_sqlancer_result.py index d0e41643467..3bed4578565 100755 --- a/docker/test/sqlancer/process_sqlancer_result.py +++ b/docker/test/sqlancer/process_sqlancer_result.py @@ -11,14 +11,14 @@ def process_result(result_folder): summary = [] paths = [] tests = [ - "TLPWhere", + "TLPAggregate", + "TLPDistinct", "TLPGroupBy", "TLPHaving", + "TLPWhere", "TLPWhereGroupBy", - "TLPDistinct", - "TLPAggregate", + "NoREC", ] - failed_tests = [] for test in tests: diff --git a/docker/test/sqlancer/run.sh b/docker/test/sqlancer/run.sh index 8476afaefb9..4f73ce0359e 100755 --- a/docker/test/sqlancer/run.sh +++ b/docker/test/sqlancer/run.sh @@ -39,12 +39,13 @@ export TIMEOUT=300 export NUM_QUERIES=1000 export NUM_THREADS=30 -( java -jar target/sqlancer-*.jar --num-threads $NUM_THREADS --timeout-seconds $TIMEOUT --num-queries $NUM_QUERIES --username default --password "" clickhouse --oracle TLPWhere | tee /workspace/TLPWhere.out ) 3>&1 1>&2 2>&3 | tee /workspace/TLPWhere.err -( java -jar target/sqlancer-*.jar --num-threads $NUM_THREADS --timeout-seconds $TIMEOUT --num-queries $NUM_QUERIES --username default --password "" clickhouse --oracle TLPGroupBy | tee /workspace/TLPGroupBy.out ) 3>&1 1>&2 2>&3 | tee /workspace/TLPGroupBy.err -( java -jar target/sqlancer-*.jar --num-threads $NUM_THREADS --timeout-seconds $TIMEOUT --num-queries $NUM_QUERIES --username default --password "" clickhouse --oracle TLPHaving | tee /workspace/TLPHaving.out ) 3>&1 1>&2 2>&3 | tee /workspace/TLPHaving.err -( java -jar target/sqlancer-*.jar --num-threads $NUM_THREADS --timeout-seconds $TIMEOUT --num-queries $NUM_QUERIES --username default --password "" clickhouse --oracle TLPWhere --oracle TLPGroupBy | tee /workspace/TLPWhereGroupBy.out ) 3>&1 1>&2 2>&3 | tee /workspace/TLPWhereGroupBy.err -( java -jar target/sqlancer-*.jar --num-threads $NUM_THREADS --timeout-seconds $TIMEOUT --num-queries $NUM_QUERIES --username default --password "" clickhouse --oracle TLPDistinct | tee /workspace/TLPDistinct.out ) 3>&1 1>&2 2>&3 | tee /workspace/TLPDistinct.err -( java -jar target/sqlancer-*.jar --num-threads $NUM_THREADS --timeout-seconds $TIMEOUT --num-queries $NUM_QUERIES --username default --password "" clickhouse --oracle TLPAggregate | tee /workspace/TLPAggregate.out ) 3>&1 1>&2 2>&3 | tee /workspace/TLPAggregate.err +( java -jar target/sqlancer-*.jar --log-each-select true --print-failed false --num-threads $NUM_THREADS --timeout-seconds $TIMEOUT --num-queries $NUM_QUERIES --username default --password "" clickhouse --oracle TLPWhere | tee /workspace/TLPWhere.out ) 3>&1 1>&2 2>&3 | tee /workspace/TLPWhere.err +( java -jar target/sqlancer-*.jar --log-each-select true --print-failed false --num-threads $NUM_THREADS --timeout-seconds $TIMEOUT --num-queries $NUM_QUERIES --username default --password "" clickhouse --oracle TLPGroupBy | tee /workspace/TLPGroupBy.out ) 3>&1 1>&2 2>&3 | tee /workspace/TLPGroupBy.err +( java -jar target/sqlancer-*.jar --log-each-select true --print-failed false --num-threads $NUM_THREADS --timeout-seconds $TIMEOUT --num-queries $NUM_QUERIES --username default --password "" clickhouse --oracle TLPHaving | tee /workspace/TLPHaving.out ) 3>&1 1>&2 2>&3 | tee /workspace/TLPHaving.err +( java -jar target/sqlancer-*.jar --log-each-select true --print-failed false --num-threads $NUM_THREADS --timeout-seconds $TIMEOUT --num-queries $NUM_QUERIES --username default --password "" clickhouse --oracle TLPWhere --oracle TLPGroupBy | tee /workspace/TLPWhereGroupBy.out ) 3>&1 1>&2 2>&3 | tee /workspace/TLPWhereGroupBy.err +( java -jar target/sqlancer-*.jar --log-each-select true --print-failed false --num-threads $NUM_THREADS --timeout-seconds $TIMEOUT --num-queries $NUM_QUERIES --username default --password "" clickhouse --oracle TLPDistinct | tee /workspace/TLPDistinct.out ) 3>&1 1>&2 2>&3 | tee /workspace/TLPDistinct.err +( java -jar target/sqlancer-*.jar --log-each-select true --print-failed false --num-threads $NUM_THREADS --timeout-seconds $TIMEOUT --num-queries $NUM_QUERIES --username default --password "" clickhouse --oracle TLPAggregate | tee /workspace/TLPAggregate.out ) 3>&1 1>&2 2>&3 | tee /workspace/TLPAggregate.err +( java -jar target/sqlancer-*.jar --log-each-select true --print-failed false --num-threads $NUM_THREADS --timeout-seconds $TIMEOUT --num-queries $NUM_QUERIES --username default --password "" clickhouse --oracle TLPAggregate | tee /workspace/NoREC.out ) 3>&1 1>&2 2>&3 | tee /workspace/NoREC.err ls /workspace pkill -F /workspace/clickhouse-server.pid diff --git a/tests/ci/sqlancer_check.py b/tests/ci/sqlancer_check.py index eced80db9b7..3ea00d60b44 100644 --- a/tests/ci/sqlancer_check.py +++ b/tests/ci/sqlancer_check.py @@ -111,26 +111,28 @@ if __name__ == "__main__": check_name.lower().replace("(", "").replace(")", "").replace(" ", "") ) s3_prefix = f"{pr_info.number}/{pr_info.sha}/{check_name_lower}/" + + tests = [ + "TLPAggregate", + "TLPDistinct", + "TLPGroupBy", + "TLPHaving", + "TLPWhere", + "TLPWhereGroupBy", + "NoREC", + ] + paths = { "runlog.log": run_log_path, - "TLPWhere": os.path.join(workspace_path, "TLPWhere.log"), - "TLPAggregate.err": os.path.join(workspace_path, "TLPAggregate.err"), - "TLPAggregate.out": os.path.join(workspace_path, "TLPAggregate.out"), - "TLPDistinct.err": os.path.join(workspace_path, "TLPDistinct.err"), - "TLPDistinct.out": os.path.join(workspace_path, "TLPDistinct.out"), - "TLPGroupBy.err": os.path.join(workspace_path, "TLPGroupBy.err"), - "TLPGroupBy.out": os.path.join(workspace_path, "TLPGroupBy.out"), - "TLPHaving.err": os.path.join(workspace_path, "TLPHaving.err"), - "TLPHaving.out": os.path.join(workspace_path, "TLPHaving.out"), - "TLPWhere.err": os.path.join(workspace_path, "TLPWhere.err"), - "TLPWhere.out": os.path.join(workspace_path, "TLPWhere.out"), - "TLPWhereGroupBy.err": os.path.join(workspace_path, "TLPWhereGroupBy.err"), - "TLPWhereGroupBy.out": os.path.join(workspace_path, "TLPWhereGroupBy.out"), "clickhouse-server.log": os.path.join(workspace_path, "clickhouse-server"), - "clickhouse-server.log.err": os.path.join(workspace_path, "clickhouse-server"), "stderr.log": os.path.join(workspace_path, "stderr.log"), "stdout.log": os.path.join(workspace_path, "stdout.log"), } + for t in tests: + err_name = f"{t}.err" + log_name = f"{t}.err" + paths[err_name] = os.path.join(workspace_path, err_name) + paths[log_name] = os.path.join(workspace_path, log_name) s3_helper = S3Helper() for f in paths: From 611c2e2bd75614e8eeb9d10933d76d6c5cd9f89b Mon Sep 17 00:00:00 2001 From: Antonio Andelic Date: Tue, 25 Oct 2022 13:34:34 +0000 Subject: [PATCH 097/266] Support for optimizing old parts for entire partition only --- .../MergeTree/MergeTreeDataMergerMutator.cpp | 27 ++++++++++++++++++- src/Storages/MergeTree/MergeTreeSettings.h | 1 + .../test.py | 24 ++++++++++++----- 3 files changed, 44 insertions(+), 8 deletions(-) diff --git a/src/Storages/MergeTree/MergeTreeDataMergerMutator.cpp b/src/Storages/MergeTree/MergeTreeDataMergerMutator.cpp index b0ef1522685..27000796343 100644 --- a/src/Storages/MergeTree/MergeTreeDataMergerMutator.cpp +++ b/src/Storages/MergeTree/MergeTreeDataMergerMutator.cpp @@ -214,6 +214,14 @@ SelectPartsDecision MergeTreeDataMergerMutator::selectPartsToMerge( /// Previous part only in boundaries of partition frame const MergeTreeData::DataPartPtr * prev_part = nullptr; + /// collect min_age for each partition while iterating parts + struct PartitionInfo + { + time_t min_age{std::numeric_limits::max()}; + }; + + std::unordered_map partitions_info; + size_t parts_selected_precondition = 0; for (const MergeTreeData::DataPartPtr & part : data_parts) { @@ -277,6 +285,9 @@ SelectPartsDecision MergeTreeDataMergerMutator::selectPartsToMerge( part_info.compression_codec_desc = part->default_codec->getFullCodecDesc(); part_info.shall_participate_in_merges = has_volumes_with_disabled_merges ? part->shallParticipateInMerges(storage_policy) : true; + auto & partition_info = partitions_info[partition_id]; + partition_info.min_age = std::min(partition_info.min_age, part_info.age); + ++parts_selected_precondition; parts_ranges.back().emplace_back(part_info); @@ -333,7 +344,8 @@ SelectPartsDecision MergeTreeDataMergerMutator::selectPartsToMerge( SimpleMergeSelector::Settings merge_settings; /// Override value from table settings merge_settings.max_parts_to_merge_at_once = data_settings->max_parts_to_merge_at_once; - merge_settings.min_age_to_force_merge = data_settings->min_age_to_force_merge_seconds; + if (!data_settings->min_age_to_force_merge_on_partition_only) + merge_settings.min_age_to_force_merge = data_settings->min_age_to_force_merge_seconds; if (aggressive) merge_settings.base = 1; @@ -347,6 +359,19 @@ SelectPartsDecision MergeTreeDataMergerMutator::selectPartsToMerge( if (parts_to_merge.empty()) { + if (data_settings->min_age_to_force_merge_on_partition_only && data_settings->min_age_to_force_merge_seconds) + { + auto best_partition_it = std::max_element( + partitions_info.begin(), + partitions_info.end(), + [](const auto & e1, const auto & e2) { return e1.second.min_age > e2.second.min_age; }); + + if (best_partition_it != partitions_info.end() + && static_cast(best_partition_it->second.min_age) >= data_settings->min_age_to_force_merge_seconds) + return selectAllPartsToMergeWithinPartition( + future_part, can_merge_callback, best_partition_it->first, true, metadata_snapshot, txn, out_disable_reason); + } + if (out_disable_reason) *out_disable_reason = "There is no need to merge parts according to merge selector algorithm"; return SelectPartsDecision::CANNOT_SELECT; diff --git a/src/Storages/MergeTree/MergeTreeSettings.h b/src/Storages/MergeTree/MergeTreeSettings.h index 3fecb85f484..844c1ddbfe5 100644 --- a/src/Storages/MergeTree/MergeTreeSettings.h +++ b/src/Storages/MergeTree/MergeTreeSettings.h @@ -63,6 +63,7 @@ struct Settings; M(UInt64, merge_tree_clear_old_parts_interval_seconds, 1, "The period of executing the clear old parts operation in background.", 0) \ M(UInt64, merge_tree_clear_old_broken_detached_parts_ttl_timeout_seconds, 1ULL * 3600 * 24 * 30, "Remove old broken detached parts in the background if they remained intouched for a specified by this setting period of time.", 0) \ M(UInt64, min_age_to_force_merge_seconds, 0, "If all parts in a certain range are older than this value, range will be always eligible for merging. Set to 0 to disable.", 0) \ + M(Bool, min_age_to_force_merge_on_partition_only, false, "Whether min_age_to_force_merge_seconds should be applied only on the entire partition and not on subset.", false) \ M(UInt64, merge_tree_enable_clear_old_broken_detached, false, "Enable clearing old broken detached parts operation in background.", 0) \ M(Bool, remove_rolled_back_parts_immediately, 1, "Setting for an incomplete experimental feature.", 0) \ \ diff --git a/tests/integration/test_merge_tree_optimize_old_parts/test.py b/tests/integration/test_merge_tree_optimize_old_parts/test.py index 7b386eba2c4..87e0ecd8108 100644 --- a/tests/integration/test_merge_tree_optimize_old_parts/test.py +++ b/tests/integration/test_merge_tree_optimize_old_parts/test.py @@ -13,7 +13,7 @@ node = cluster.add_instance( @pytest.fixture(scope="module") -def start_cluster(): +def started_cluster(): try: cluster.start() @@ -42,7 +42,7 @@ def check_expected_part_number(seconds, table_name, expected): assert ok -def test_without_force_merge_old_parts(start_cluster): +def test_without_force_merge_old_parts(started_cluster): node.query( "CREATE TABLE test_without_merge (i Int64) ENGINE = MergeTree ORDER BY i;" ) @@ -60,13 +60,18 @@ def test_without_force_merge_old_parts(start_cluster): node.query("DROP TABLE test_without_merge;") -def test_force_merge_old_parts(start_cluster): +@pytest.mark.parametrize("partition_only", ["True", "False"]) +def test_force_merge_old_parts(started_cluster, partition_only): node.query( - "CREATE TABLE test_with_merge (i Int64) ENGINE = MergeTree ORDER BY i SETTINGS min_age_to_force_merge_seconds=5;" + "CREATE TABLE test_with_merge (i Int64) " + "ENGINE = MergeTree " + "ORDER BY i " + f"SETTINGS min_age_to_force_merge_seconds=5, min_age_to_force_merge_on_partition_only={partition_only};" ) node.query("INSERT INTO test_with_merge SELECT 1") node.query("INSERT INTO test_with_merge SELECT 2") node.query("INSERT INTO test_with_merge SELECT 3") + assert get_part_number("test_with_merge") == TSV("""3\n""") expected = TSV("""1\n""") check_expected_part_number(10, "test_with_merge", expected) @@ -74,15 +79,20 @@ def test_force_merge_old_parts(start_cluster): node.query("DROP TABLE test_with_merge;") -def test_force_merge_old_parts_replicated_merge_tree(start_cluster): +@pytest.mark.parametrize("partition_only", ["True", "False"]) +def test_force_merge_old_parts_replicated_merge_tree(started_cluster, partition_only): node.query( - "CREATE TABLE test_replicated (i Int64) ENGINE = ReplicatedMergeTree('/clickhouse/testing/test', 'node') ORDER BY i SETTINGS min_age_to_force_merge_seconds=5;" + "CREATE TABLE test_replicated (i Int64) " + "ENGINE = ReplicatedMergeTree('/clickhouse/testing/test', 'node') " + "ORDER BY i " + f"SETTINGS min_age_to_force_merge_seconds=5, min_age_to_force_merge_on_partition_only={partition_only};" ) node.query("INSERT INTO test_replicated SELECT 1") node.query("INSERT INTO test_replicated SELECT 2") node.query("INSERT INTO test_replicated SELECT 3") + assert get_part_number("test_replicated") == TSV("""3\n""") expected = TSV("""1\n""") check_expected_part_number(10, "test_replicated", expected) - node.query("DROP TABLE test_replicated;") + node.query("DROP TABLE test_replicated SYNC;") From 302df3af122533a8ddd683100395fdcbd71b55ba Mon Sep 17 00:00:00 2001 From: Yatsishin Ilya <2159081+qoega@users.noreply.github.com> Date: Tue, 25 Oct 2022 16:34:12 +0000 Subject: [PATCH 098/266] fix? --- tests/ci/sqlancer_check.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/ci/sqlancer_check.py b/tests/ci/sqlancer_check.py index 3ea00d60b44..c06760d3627 100644 --- a/tests/ci/sqlancer_check.py +++ b/tests/ci/sqlancer_check.py @@ -94,7 +94,7 @@ if __name__ == "__main__": run_command = get_run_command(build_url, workspace_path, docker_image) logging.info("Going to run %s", run_command) - run_log_path = os.path.join(temp_path, "runlog.log") + run_log_path = os.path.join(workspace_path, "runlog.log") with open(run_log_path, "w", encoding="utf-8") as log: with subprocess.Popen( run_command, shell=True, stderr=log, stdout=log @@ -123,7 +123,7 @@ if __name__ == "__main__": ] paths = { - "runlog.log": run_log_path, + # "runlog.log": run_log_path, "clickhouse-server.log": os.path.join(workspace_path, "clickhouse-server"), "stderr.log": os.path.join(workspace_path, "stderr.log"), "stdout.log": os.path.join(workspace_path, "stdout.log"), From a9a799ac89816b7a8a82c227aeb5533892168d0e Mon Sep 17 00:00:00 2001 From: Nikolay Degterinsky Date: Wed, 26 Oct 2022 05:20:34 +0000 Subject: [PATCH 099/266] Add test --- .../0_stateless/02473_infile_progress.py | 42 +++++++++++++++++++ .../02473_infile_progress.reference | 0 2 files changed, 42 insertions(+) create mode 100755 tests/queries/0_stateless/02473_infile_progress.py create mode 100644 tests/queries/0_stateless/02473_infile_progress.reference diff --git a/tests/queries/0_stateless/02473_infile_progress.py b/tests/queries/0_stateless/02473_infile_progress.py new file mode 100755 index 00000000000..2d6493fe4a5 --- /dev/null +++ b/tests/queries/0_stateless/02473_infile_progress.py @@ -0,0 +1,42 @@ +#!/usr/bin/env python3 +# Tags: no-replicated-database, no-parallel, no-fasttest + +import os +import sys +import signal + +CURDIR = os.path.dirname(os.path.realpath(__file__)) +sys.path.insert(0, os.path.join(CURDIR, "helpers")) + +from client import client, prompt, end_of_block + +log = None +# uncomment the line below for debugging +# log=sys.stdout + +with client(name="client>", log=log) as client1: + filename = os.environ["CLICKHOUSE_TMP"] + "/infile_progress.tsv" + + client1.expect(prompt) + client1.send("DROP TABLE IF EXISTS test.infile_progress") + client1.expect(prompt) + client1.send(f"SELECT number FROM numbers(5) INTO OUTFILE '{filename}'") + client1.expect(prompt) + client1.send( + "CREATE TABLE test.infile_progress (a Int32) Engine=MergeTree order by tuple()" + ) + client1.expect(prompt) + client1.send(f"INSERT INTO test.infile_progress FROM INFILE '{filename}'") + client1.expect("Progress: 5.00 rows.*\)") + client1.expect(prompt) + + # send Ctrl-C + client1.send("\x03", eol="") + match = client1.expect("(%s)|([#\$] )" % prompt) + if match.groups()[1]: + client1.send(client1.command) + client1.expect(prompt) + client1.send("DROP TABLE test.infile_progress") + client1.expect(prompt) + + os.remove(filename) diff --git a/tests/queries/0_stateless/02473_infile_progress.reference b/tests/queries/0_stateless/02473_infile_progress.reference new file mode 100644 index 00000000000..e69de29bb2d From 2254bef74a105ec91389d1c84a6805119f6b4fca Mon Sep 17 00:00:00 2001 From: taiyang-li <654010905@qq.com> Date: Tue, 25 Oct 2022 12:07:07 +0800 Subject: [PATCH 100/266] implement function ascii --- .../functions/string-functions.md | 10 +++ src/Functions/ascii.cpp | 74 +++++++++++++++++++ .../queries/0_stateless/02353_ascii.reference | 2 + tests/queries/0_stateless/02353_ascii.sql | 2 + 4 files changed, 88 insertions(+) create mode 100644 src/Functions/ascii.cpp create mode 100644 tests/queries/0_stateless/02353_ascii.reference create mode 100644 tests/queries/0_stateless/02353_ascii.sql diff --git a/docs/en/sql-reference/functions/string-functions.md b/docs/en/sql-reference/functions/string-functions.md index a8ba4843279..982ba05f494 100644 --- a/docs/en/sql-reference/functions/string-functions.md +++ b/docs/en/sql-reference/functions/string-functions.md @@ -1150,3 +1150,13 @@ A text with tags . The content within CDATA Do Nothing for 2 Minutes 2:00   ``` + +## ascii(s) {#ascii} + +Returns the ASCII code point of the first character of str. The result type is Int32. + +If s is empty, the result is 0. If the first character is not an ASCII character or part of the Latin-1 Supplement range of UTF-16, the result is undefined. + + + + diff --git a/src/Functions/ascii.cpp b/src/Functions/ascii.cpp new file mode 100644 index 00000000000..a8a6b9f7226 --- /dev/null +++ b/src/Functions/ascii.cpp @@ -0,0 +1,74 @@ +#include +#include +#include + +namespace DB +{ + +namespace ErrorCodes +{ + extern const int ILLEGAL_TYPE_OF_ARGUMENT; + extern const int NOT_IMPLEMENTED; +} + +struct AsciiName +{ + static constexpr auto name = "ascii"; +}; + + +struct AsciiImpl +{ + static constexpr auto is_fixed_to_constant = false; + using ReturnType = Int32; + + + static void vector(const ColumnString::Chars & data, const ColumnString::Offsets & offsets, PaddedPODArray & res) + { + size_t size = offsets.size(); + + ColumnString::Offset prev_offset = 0; + for (size_t i = 0; i < size; ++i) + { + res[i] = doAscii(data, prev_offset, offsets[i] - prev_offset - 1); + prev_offset = offsets[i]; + } + } + + [[noreturn]] static void vectorFixedToConstant(const ColumnString::Chars & /*data*/, size_t /*n*/, Int32 & /*res*/) + { + throw Exception("vectorFixedToConstant not implemented for function " + std::string(AsciiName::name), ErrorCodes::NOT_IMPLEMENTED); + } + + static void vectorFixedToVector(const ColumnString::Chars & data, size_t n, PaddedPODArray & res) + { + size_t size = data.size() / n; + + for (size_t i = 0; i < size; ++i) + { + res[i] = doAscii(data, i * n, n); + } + } + + [[noreturn]] static void array(const ColumnString::Offsets & /*offsets*/, PaddedPODArray & /*res*/) + { + throw Exception("Cannot apply function " + std::string(AsciiName::name) + " to Array argument", ErrorCodes::ILLEGAL_TYPE_OF_ARGUMENT); + } + + [[noreturn]] static void uuid(const ColumnUUID::Container & /*offsets*/, size_t /*n*/, PaddedPODArray & /*res*/) + { + throw Exception("Cannot apply function " + std::string(AsciiName::name) + " to UUID argument", ErrorCodes::ILLEGAL_TYPE_OF_ARGUMENT); + } + +private: + static Int32 doAscii(const ColumnString::Chars & buf, size_t offset, size_t size) { return size ? static_cast(buf[offset]) : 0; } +}; + +using FunctionAscii = FunctionStringOrArrayToT; + +REGISTER_FUNCTION(Ascii) +{ + factory.registerFunction({}, FunctionFactory::CaseInsensitive); +} + +} diff --git a/tests/queries/0_stateless/02353_ascii.reference b/tests/queries/0_stateless/02353_ascii.reference new file mode 100644 index 00000000000..d44c5c7d87e --- /dev/null +++ b/tests/queries/0_stateless/02353_ascii.reference @@ -0,0 +1,2 @@ +50 +0 diff --git a/tests/queries/0_stateless/02353_ascii.sql b/tests/queries/0_stateless/02353_ascii.sql new file mode 100644 index 00000000000..c1c5d60c447 --- /dev/null +++ b/tests/queries/0_stateless/02353_ascii.sql @@ -0,0 +1,2 @@ +SELECT ascii('234'); +SELECT ascii(''); From add5360a1b17d794760d4f0eac9834c931a10fac Mon Sep 17 00:00:00 2001 From: taiyang-li <654010905@qq.com> Date: Tue, 25 Oct 2022 12:07:07 +0800 Subject: [PATCH 101/266] implement function ascii --- .../functions/string-functions.md | 10 +++ src/Functions/ascii.cpp | 74 +++++++++++++++++++ .../queries/0_stateless/02353_ascii.reference | 2 + tests/queries/0_stateless/02353_ascii.sql | 2 + 4 files changed, 88 insertions(+) create mode 100644 src/Functions/ascii.cpp create mode 100644 tests/queries/0_stateless/02353_ascii.reference create mode 100644 tests/queries/0_stateless/02353_ascii.sql diff --git a/docs/en/sql-reference/functions/string-functions.md b/docs/en/sql-reference/functions/string-functions.md index a8ba4843279..982ba05f494 100644 --- a/docs/en/sql-reference/functions/string-functions.md +++ b/docs/en/sql-reference/functions/string-functions.md @@ -1150,3 +1150,13 @@ A text with tags . The content within CDATA Do Nothing for 2 Minutes 2:00   ``` + +## ascii(s) {#ascii} + +Returns the ASCII code point of the first character of str. The result type is Int32. + +If s is empty, the result is 0. If the first character is not an ASCII character or part of the Latin-1 Supplement range of UTF-16, the result is undefined. + + + + diff --git a/src/Functions/ascii.cpp b/src/Functions/ascii.cpp new file mode 100644 index 00000000000..a8a6b9f7226 --- /dev/null +++ b/src/Functions/ascii.cpp @@ -0,0 +1,74 @@ +#include +#include +#include + +namespace DB +{ + +namespace ErrorCodes +{ + extern const int ILLEGAL_TYPE_OF_ARGUMENT; + extern const int NOT_IMPLEMENTED; +} + +struct AsciiName +{ + static constexpr auto name = "ascii"; +}; + + +struct AsciiImpl +{ + static constexpr auto is_fixed_to_constant = false; + using ReturnType = Int32; + + + static void vector(const ColumnString::Chars & data, const ColumnString::Offsets & offsets, PaddedPODArray & res) + { + size_t size = offsets.size(); + + ColumnString::Offset prev_offset = 0; + for (size_t i = 0; i < size; ++i) + { + res[i] = doAscii(data, prev_offset, offsets[i] - prev_offset - 1); + prev_offset = offsets[i]; + } + } + + [[noreturn]] static void vectorFixedToConstant(const ColumnString::Chars & /*data*/, size_t /*n*/, Int32 & /*res*/) + { + throw Exception("vectorFixedToConstant not implemented for function " + std::string(AsciiName::name), ErrorCodes::NOT_IMPLEMENTED); + } + + static void vectorFixedToVector(const ColumnString::Chars & data, size_t n, PaddedPODArray & res) + { + size_t size = data.size() / n; + + for (size_t i = 0; i < size; ++i) + { + res[i] = doAscii(data, i * n, n); + } + } + + [[noreturn]] static void array(const ColumnString::Offsets & /*offsets*/, PaddedPODArray & /*res*/) + { + throw Exception("Cannot apply function " + std::string(AsciiName::name) + " to Array argument", ErrorCodes::ILLEGAL_TYPE_OF_ARGUMENT); + } + + [[noreturn]] static void uuid(const ColumnUUID::Container & /*offsets*/, size_t /*n*/, PaddedPODArray & /*res*/) + { + throw Exception("Cannot apply function " + std::string(AsciiName::name) + " to UUID argument", ErrorCodes::ILLEGAL_TYPE_OF_ARGUMENT); + } + +private: + static Int32 doAscii(const ColumnString::Chars & buf, size_t offset, size_t size) { return size ? static_cast(buf[offset]) : 0; } +}; + +using FunctionAscii = FunctionStringOrArrayToT; + +REGISTER_FUNCTION(Ascii) +{ + factory.registerFunction({}, FunctionFactory::CaseInsensitive); +} + +} diff --git a/tests/queries/0_stateless/02353_ascii.reference b/tests/queries/0_stateless/02353_ascii.reference new file mode 100644 index 00000000000..d44c5c7d87e --- /dev/null +++ b/tests/queries/0_stateless/02353_ascii.reference @@ -0,0 +1,2 @@ +50 +0 diff --git a/tests/queries/0_stateless/02353_ascii.sql b/tests/queries/0_stateless/02353_ascii.sql new file mode 100644 index 00000000000..c1c5d60c447 --- /dev/null +++ b/tests/queries/0_stateless/02353_ascii.sql @@ -0,0 +1,2 @@ +SELECT ascii('234'); +SELECT ascii(''); From b096edc995f95f0d017a0721c7beece521262c47 Mon Sep 17 00:00:00 2001 From: Yatsishin Ilya <2159081+qoega@users.noreply.github.com> Date: Wed, 26 Oct 2022 08:30:20 +0000 Subject: [PATCH 102/266] do not upload files manually --- tests/ci/sqlancer_check.py | 23 ++++++++--------------- tests/ci/upload_result_helper.py | 2 ++ 2 files changed, 10 insertions(+), 15 deletions(-) diff --git a/tests/ci/sqlancer_check.py b/tests/ci/sqlancer_check.py index c06760d3627..1564dfc4fc4 100644 --- a/tests/ci/sqlancer_check.py +++ b/tests/ci/sqlancer_check.py @@ -122,26 +122,19 @@ if __name__ == "__main__": "NoREC", ] - paths = { - # "runlog.log": run_log_path, - "clickhouse-server.log": os.path.join(workspace_path, "clickhouse-server"), - "stderr.log": os.path.join(workspace_path, "stderr.log"), - "stdout.log": os.path.join(workspace_path, "stdout.log"), - } + paths = [ + run_log_path, + os.path.join(workspace_path, "clickhouse-server"), + os.path.join(workspace_path, "stderr.log"), + os.path.join(workspace_path, "stdout.log"), + ] for t in tests: err_name = f"{t}.err" log_name = f"{t}.err" - paths[err_name] = os.path.join(workspace_path, err_name) - paths[log_name] = os.path.join(workspace_path, log_name) + paths.append(os.path.join(workspace_path, err_name)) + paths.append(os.path.join(workspace_path, log_name)) s3_helper = S3Helper() - for f in paths: - try: - paths[f] = s3_helper.upload_test_report_to_s3(paths[f], s3_prefix + "/" + f) - except Exception as ex: - logging.info("Exception uploading file %s text %s", f, ex) - paths[f] = "" - report_url = GITHUB_RUN_URL status = "succeess" diff --git a/tests/ci/upload_result_helper.py b/tests/ci/upload_result_helper.py index 0fde4408176..6fb8982027c 100644 --- a/tests/ci/upload_result_helper.py +++ b/tests/ci/upload_result_helper.py @@ -14,6 +14,8 @@ from report import ReportColorTheme, create_test_html_report def process_logs( s3_client, additional_logs, s3_path_prefix, test_results, with_raw_logs ): + logging.info(f"Upload files to s3 {additional_logs}") + processed_logs = {} # Firstly convert paths of logs from test_results to urls to s3. for test_result in test_results: From 3794d2108943b5e7f98d9b06800308de3de6ab88 Mon Sep 17 00:00:00 2001 From: Yatsishin Ilya <2159081+qoega@users.noreply.github.com> Date: Wed, 26 Oct 2022 08:30:57 +0000 Subject: [PATCH 103/266] ignore tests tmp files --- .gitignore | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.gitignore b/.gitignore index dd632eba85d..af4615a8e6c 100644 --- a/.gitignore +++ b/.gitignore @@ -153,3 +153,6 @@ website/package-lock.json /programs/server/metadata /programs/server/store +# temporary test files +tests/queries/0_stateless/test_* +tests/queries/0_stateless/*.binary From 97aaebfa1808e9b90258a78de2927c97bae05feb Mon Sep 17 00:00:00 2001 From: Antonio Andelic Date: Wed, 26 Oct 2022 10:06:56 +0000 Subject: [PATCH 104/266] Address PR comments --- .../MergeTree/MergeTreeDataMergerMutator.cpp | 5 +- .../__init__.py | 0 .../configs/zookeeper_config.xml | 8 -- .../test.py | 98 ------------------- .../02473_optimize_old_parts.reference | 10 ++ .../0_stateless/02473_optimize_old_parts.sql | 67 +++++++++++++ 6 files changed, 80 insertions(+), 108 deletions(-) delete mode 100644 tests/integration/test_merge_tree_optimize_old_parts/__init__.py delete mode 100644 tests/integration/test_merge_tree_optimize_old_parts/configs/zookeeper_config.xml delete mode 100644 tests/integration/test_merge_tree_optimize_old_parts/test.py create mode 100644 tests/queries/0_stateless/02473_optimize_old_parts.reference create mode 100644 tests/queries/0_stateless/02473_optimize_old_parts.sql diff --git a/src/Storages/MergeTree/MergeTreeDataMergerMutator.cpp b/src/Storages/MergeTree/MergeTreeDataMergerMutator.cpp index 27000796343..0f44d1a7da3 100644 --- a/src/Storages/MergeTree/MergeTreeDataMergerMutator.cpp +++ b/src/Storages/MergeTree/MergeTreeDataMergerMutator.cpp @@ -366,8 +366,9 @@ SelectPartsDecision MergeTreeDataMergerMutator::selectPartsToMerge( partitions_info.end(), [](const auto & e1, const auto & e2) { return e1.second.min_age > e2.second.min_age; }); - if (best_partition_it != partitions_info.end() - && static_cast(best_partition_it->second.min_age) >= data_settings->min_age_to_force_merge_seconds) + assert(best_partition_it != partitions_info.end()); + + if (static_cast(best_partition_it->second.min_age) >= data_settings->min_age_to_force_merge_seconds) return selectAllPartsToMergeWithinPartition( future_part, can_merge_callback, best_partition_it->first, true, metadata_snapshot, txn, out_disable_reason); } diff --git a/tests/integration/test_merge_tree_optimize_old_parts/__init__.py b/tests/integration/test_merge_tree_optimize_old_parts/__init__.py deleted file mode 100644 index e69de29bb2d..00000000000 diff --git a/tests/integration/test_merge_tree_optimize_old_parts/configs/zookeeper_config.xml b/tests/integration/test_merge_tree_optimize_old_parts/configs/zookeeper_config.xml deleted file mode 100644 index 18412349228..00000000000 --- a/tests/integration/test_merge_tree_optimize_old_parts/configs/zookeeper_config.xml +++ /dev/null @@ -1,8 +0,0 @@ - - - - zoo1 - 2181 - - - diff --git a/tests/integration/test_merge_tree_optimize_old_parts/test.py b/tests/integration/test_merge_tree_optimize_old_parts/test.py deleted file mode 100644 index 87e0ecd8108..00000000000 --- a/tests/integration/test_merge_tree_optimize_old_parts/test.py +++ /dev/null @@ -1,98 +0,0 @@ -import pytest -import time -from helpers.client import QueryRuntimeException -from helpers.cluster import ClickHouseCluster -from helpers.test_tools import TSV - -cluster = ClickHouseCluster(__file__) -node = cluster.add_instance( - "node", - main_configs=["configs/zookeeper_config.xml"], - with_zookeeper=True, -) - - -@pytest.fixture(scope="module") -def started_cluster(): - try: - cluster.start() - - yield cluster - finally: - cluster.shutdown() - - -def get_part_number(table_name): - return TSV( - node.query( - f"SELECT count(*) FROM system.parts where table='{table_name}' and active=1" - ) - ) - - -def check_expected_part_number(seconds, table_name, expected): - ok = False - for i in range(int(seconds) * 2): - result = get_part_number(table_name) - if result == expected: - ok = True - break - else: - time.sleep(1) - assert ok - - -def test_without_force_merge_old_parts(started_cluster): - node.query( - "CREATE TABLE test_without_merge (i Int64) ENGINE = MergeTree ORDER BY i;" - ) - node.query("INSERT INTO test_without_merge SELECT 1") - node.query("INSERT INTO test_without_merge SELECT 2") - node.query("INSERT INTO test_without_merge SELECT 3") - - expected = TSV("""3\n""") - # verify that the parts don't get merged - for i in range(10): - if get_part_number("test_without_merge") != expected: - assert False - time.sleep(1) - - node.query("DROP TABLE test_without_merge;") - - -@pytest.mark.parametrize("partition_only", ["True", "False"]) -def test_force_merge_old_parts(started_cluster, partition_only): - node.query( - "CREATE TABLE test_with_merge (i Int64) " - "ENGINE = MergeTree " - "ORDER BY i " - f"SETTINGS min_age_to_force_merge_seconds=5, min_age_to_force_merge_on_partition_only={partition_only};" - ) - node.query("INSERT INTO test_with_merge SELECT 1") - node.query("INSERT INTO test_with_merge SELECT 2") - node.query("INSERT INTO test_with_merge SELECT 3") - assert get_part_number("test_with_merge") == TSV("""3\n""") - - expected = TSV("""1\n""") - check_expected_part_number(10, "test_with_merge", expected) - - node.query("DROP TABLE test_with_merge;") - - -@pytest.mark.parametrize("partition_only", ["True", "False"]) -def test_force_merge_old_parts_replicated_merge_tree(started_cluster, partition_only): - node.query( - "CREATE TABLE test_replicated (i Int64) " - "ENGINE = ReplicatedMergeTree('/clickhouse/testing/test', 'node') " - "ORDER BY i " - f"SETTINGS min_age_to_force_merge_seconds=5, min_age_to_force_merge_on_partition_only={partition_only};" - ) - node.query("INSERT INTO test_replicated SELECT 1") - node.query("INSERT INTO test_replicated SELECT 2") - node.query("INSERT INTO test_replicated SELECT 3") - assert get_part_number("test_replicated") == TSV("""3\n""") - - expected = TSV("""1\n""") - check_expected_part_number(10, "test_replicated", expected) - - node.query("DROP TABLE test_replicated SYNC;") diff --git a/tests/queries/0_stateless/02473_optimize_old_parts.reference b/tests/queries/0_stateless/02473_optimize_old_parts.reference new file mode 100644 index 00000000000..6767887ba86 --- /dev/null +++ b/tests/queries/0_stateless/02473_optimize_old_parts.reference @@ -0,0 +1,10 @@ +Without merge +6 +With merge any part range +1 +With merge partition only +1 +With merge replicated any part range +1 +With merge replicated partition only +1 diff --git a/tests/queries/0_stateless/02473_optimize_old_parts.sql b/tests/queries/0_stateless/02473_optimize_old_parts.sql new file mode 100644 index 00000000000..545bd58dddc --- /dev/null +++ b/tests/queries/0_stateless/02473_optimize_old_parts.sql @@ -0,0 +1,67 @@ +DROP TABLE IF EXISTS test_without_merge; +DROP TABLE IF EXISTS test_with_merge; +DROP TABLE IF EXISTS test_replicated; + +SELECT 'Without merge'; + +CREATE TABLE test_without_merge (i Int64) ENGINE = MergeTree ORDER BY i; +INSERT INTO test_without_merge SELECT 1; +INSERT INTO test_without_merge SELECT 2; +INSERT INTO test_without_merge SELECT 3; + +SELECT sleepEachRow(1) FROM numbers(6) FORMAT Null; +SELECT count(*) FROM system.parts where table='test_without_merge' and active=1; + +DROP TABLE test_without_merge; + +SELECT 'With merge any part range'; + +CREATE TABLE test_with_merge (i Int64) ENGINE = MergeTree ORDER BY i +SETTINGS min_age_to_force_merge_seconds=3, min_age_to_force_merge_on_partition_only=false; +INSERT INTO test_with_merge SELECT 1; +INSERT INTO test_with_merge SELECT 2; +INSERT INTO test_with_merge SELECT 3; + +SELECT sleepEachRow(1) FROM numbers(6) FORMAT Null; +SELECT count(*) FROM system.parts where table='test_with_merge' and active=1; + +DROP TABLE test_with_merge; + +SELECT 'With merge partition only'; + +CREATE TABLE test_with_merge (i Int64) ENGINE = MergeTree ORDER BY i +SETTINGS min_age_to_force_merge_seconds=3, min_age_to_force_merge_on_partition_only=true; +INSERT INTO test_with_merge SELECT 1; +INSERT INTO test_with_merge SELECT 2; +INSERT INTO test_with_merge SELECT 3; + +SELECT sleepEachRow(1) FROM numbers(6) FORMAT Null; +SELECT count(*) FROM system.parts where table='test_with_merge' and active=1; + +DROP TABLE test_with_merge; + +SELECT 'With merge replicated any part range'; + +CREATE TABLE test_replicated (i Int64) ENGINE = ReplicatedMergeTree('/clickhouse/tables/{database}/test02473', 'node') ORDER BY i +SETTINGS min_age_to_force_merge_seconds=3, min_age_to_force_merge_on_partition_only=false; +INSERT INTO test_replicated SELECT 1; +INSERT INTO test_replicated SELECT 2; +INSERT INTO test_replicated SELECT 3; + +SELECT sleepEachRow(1) FROM numbers(6) FORMAT Null; +SELECT count(*) FROM system.parts where table='test_replicated' and active=1; + +DROP TABLE test_replicated; + +SELECT 'With merge replicated partition only'; + +CREATE TABLE test_replicated (i Int64) ENGINE = ReplicatedMergeTree('/clickhouse/tables/{database}/test02473_partition_only', 'node') ORDER BY i +SETTINGS min_age_to_force_merge_seconds=3, min_age_to_force_merge_on_partition_only=true; +INSERT INTO test_replicated SELECT 1; +INSERT INTO test_replicated SELECT 2; +INSERT INTO test_replicated SELECT 3; + +SELECT sleepEachRow(1) FROM numbers(6) FORMAT Null; +SELECT count(*) FROM system.parts where table='test_replicated' and active=1; + +DROP TABLE test_replicated; From 48c37c52e6c2e60423e399f0f457c6fd0bf8dbcb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=9D=8E=E6=89=AC?= <654010905@qq.com> Date: Wed, 26 Oct 2022 18:18:04 +0800 Subject: [PATCH 105/266] Update src/Functions/ascii.cpp Co-authored-by: Vladimir C --- src/Functions/ascii.cpp | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/src/Functions/ascii.cpp b/src/Functions/ascii.cpp index a8a6b9f7226..592cbe5f1c4 100644 --- a/src/Functions/ascii.cpp +++ b/src/Functions/ascii.cpp @@ -68,7 +68,16 @@ using FunctionAscii = FunctionStringOrArrayToT({}, FunctionFactory::CaseInsensitive); + factory.registerFunction( + { + R"( +Returns the ASCII code point of the first character of str. The result type is Int32. + +If s is empty, the result is 0. If the first character is not an ASCII character or part of the Latin-1 Supplement range of UTF-16, the result is undefined) + )", + Documentation::Examples{{"ascii", "SELECT ascii('234')"}}, + Documentation::Categories{"String"} + }, FunctionFactory::CaseInsensitive); } } From c7e5eb756ba0ca32d66ee50db80cfdf3cd765cfc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=9D=8E=E6=89=AC?= <654010905@qq.com> Date: Wed, 26 Oct 2022 18:18:15 +0800 Subject: [PATCH 106/266] Update src/Functions/ascii.cpp Co-authored-by: Vladimir C --- src/Functions/ascii.cpp | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/Functions/ascii.cpp b/src/Functions/ascii.cpp index 592cbe5f1c4..38ba7f0bbac 100644 --- a/src/Functions/ascii.cpp +++ b/src/Functions/ascii.cpp @@ -61,7 +61,10 @@ struct AsciiImpl } private: - static Int32 doAscii(const ColumnString::Chars & buf, size_t offset, size_t size) { return size ? static_cast(buf[offset]) : 0; } + static Int32 doAscii(const ColumnString::Chars & buf, size_t offset, size_t size) + { + return size ? static_cast(buf[offset]) : 0; + } }; using FunctionAscii = FunctionStringOrArrayToT; From acfea0e2df1c9a41ee58f054c462ca9b70ab3cd0 Mon Sep 17 00:00:00 2001 From: Yatsishin Ilya <2159081+qoega@users.noreply.github.com> Date: Wed, 26 Oct 2022 11:21:33 +0000 Subject: [PATCH 107/266] style --- tests/ci/upload_result_helper.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/ci/upload_result_helper.py b/tests/ci/upload_result_helper.py index 6fb8982027c..f47d3a68ee8 100644 --- a/tests/ci/upload_result_helper.py +++ b/tests/ci/upload_result_helper.py @@ -14,7 +14,7 @@ from report import ReportColorTheme, create_test_html_report def process_logs( s3_client, additional_logs, s3_path_prefix, test_results, with_raw_logs ): - logging.info(f"Upload files to s3 {additional_logs}") + logging.info("Upload files to s3 %s", additional_logs") processed_logs = {} # Firstly convert paths of logs from test_results to urls to s3. From fa402d8967a3be21541ac1f679b24092ea9b8b03 Mon Sep 17 00:00:00 2001 From: Yatsishin Ilya <2159081+qoega@users.noreply.github.com> Date: Wed, 26 Oct 2022 12:01:12 +0000 Subject: [PATCH 108/266] fix --- tests/ci/upload_result_helper.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/ci/upload_result_helper.py b/tests/ci/upload_result_helper.py index f47d3a68ee8..e145df02f80 100644 --- a/tests/ci/upload_result_helper.py +++ b/tests/ci/upload_result_helper.py @@ -14,7 +14,7 @@ from report import ReportColorTheme, create_test_html_report def process_logs( s3_client, additional_logs, s3_path_prefix, test_results, with_raw_logs ): - logging.info("Upload files to s3 %s", additional_logs") + logging.info("Upload files to s3 %s", additional_logs) processed_logs = {} # Firstly convert paths of logs from test_results to urls to s3. From 9dee90cd429fa60da2a5e5bab9daf83a16fc9206 Mon Sep 17 00:00:00 2001 From: Yatsishin Ilya <2159081+qoega@users.noreply.github.com> Date: Wed, 26 Oct 2022 15:06:01 +0000 Subject: [PATCH 109/266] fix path --- tests/ci/sqlancer_check.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/ci/sqlancer_check.py b/tests/ci/sqlancer_check.py index 1564dfc4fc4..e8768982417 100644 --- a/tests/ci/sqlancer_check.py +++ b/tests/ci/sqlancer_check.py @@ -124,7 +124,7 @@ if __name__ == "__main__": paths = [ run_log_path, - os.path.join(workspace_path, "clickhouse-server"), + os.path.join(workspace_path, "clickhouse-server.log"), os.path.join(workspace_path, "stderr.log"), os.path.join(workspace_path, "stdout.log"), ] From 6a8fa2d4a5e922fe9dadd322429e5a88e4df1d30 Mon Sep 17 00:00:00 2001 From: Smita Kulkarni Date: Wed, 26 Oct 2022 18:45:23 +0200 Subject: [PATCH 110/266] Added new field allow_readonly in system.table_functions to allow using table functions in readonly mode. Implementation: * Added a new field allow_readonly to table system.table_functions. * Updated to use new field allow_readonly to allow using table functions in readonly mode. * Added TableFunctionProperties similar to AggregateFunctionProperties. * The functions allowed in readonly mode are in this set table_functions_allowed_in_readonly_mode. Testing: * Added a test for filesystem tests/queries/0_stateless/02473_functions_in_readonly_mode.sh Documentation: * Updated the english documentation for Table Functions. --- .../en/sql-reference/table-functions/index.md | 4 ++ .../System/StorageSystemTableFunctions.cpp | 27 ++++++++++--- src/TableFunctions/ITableFunction.cpp | 5 ++- src/TableFunctions/ITableFunction.h | 9 +++++ src/TableFunctions/TableFunctionFactory.cpp | 40 ++++++++++++++----- src/TableFunctions/TableFunctionFactory.h | 24 +++++++++-- ...02473_functions_in_readonly_mode.reference | 3 ++ .../02473_functions_in_readonly_mode.sql | 5 +++ 8 files changed, 97 insertions(+), 20 deletions(-) create mode 100644 tests/queries/0_stateless/02473_functions_in_readonly_mode.reference create mode 100644 tests/queries/0_stateless/02473_functions_in_readonly_mode.sql diff --git a/docs/en/sql-reference/table-functions/index.md b/docs/en/sql-reference/table-functions/index.md index d09adcd13d6..94b23bc695c 100644 --- a/docs/en/sql-reference/table-functions/index.md +++ b/docs/en/sql-reference/table-functions/index.md @@ -39,3 +39,7 @@ You can’t use table functions if the [allow_ddl](../../operations/settings/per | [s3](../../sql-reference/table-functions/s3.md) | Creates a [S3](../../engines/table-engines/integrations/s3.md)-engine table. | | [sqlite](../../sql-reference/table-functions/sqlite.md) | Creates a [sqlite](../../engines/table-engines/integrations/sqlite.md)-engine table. | +:::note +Only these table functions are enabled in readonly mode : +null, view, viewIfPermitted, numbers, numbers_mt, generateRandom, values, cluster, clusterAllReplicas +::: \ No newline at end of file diff --git a/src/Storages/System/StorageSystemTableFunctions.cpp b/src/Storages/System/StorageSystemTableFunctions.cpp index 308cbc5686d..07a504edc5e 100644 --- a/src/Storages/System/StorageSystemTableFunctions.cpp +++ b/src/Storages/System/StorageSystemTableFunctions.cpp @@ -1,16 +1,23 @@ #include - #include +#include + namespace DB { +namespace ErrorCodes +{ + extern const int UNKNOWN_FUNCTION; +} + NamesAndTypesList StorageSystemTableFunctions::getNamesAndTypes() { return - { - {"name", std::make_shared()}, - {"description", std::make_shared()} - }; + { + {"name", std::make_shared()}, + {"description", std::make_shared()}, + {"allow_readonly", std::make_shared()} + }; } void StorageSystemTableFunctions::fillData(MutableColumns & res_columns, ContextPtr, const SelectQueryInfo &) const @@ -20,7 +27,15 @@ void StorageSystemTableFunctions::fillData(MutableColumns & res_columns, Context for (const auto & function_name : functions_names) { res_columns[0]->insert(function_name); - res_columns[1]->insert(factory.getDocumentation(function_name).description); + + auto properties = factory.tryGetProperties(function_name); + if (properties) + { + res_columns[1]->insert(properties->documentation.description); + res_columns[2]->insert(properties->allow_readonly); + } + else + throw Exception(ErrorCodes::UNKNOWN_FUNCTION, "Unknown table function {}", function_name); } } diff --git a/src/TableFunctions/ITableFunction.cpp b/src/TableFunctions/ITableFunction.cpp index 82b6230dc30..da0de7e47f6 100644 --- a/src/TableFunctions/ITableFunction.cpp +++ b/src/TableFunctions/ITableFunction.cpp @@ -4,6 +4,7 @@ #include #include #include +#include namespace ProfileEvents @@ -25,8 +26,8 @@ StoragePtr ITableFunction::execute(const ASTPtr & ast_function, ContextPtr conte ProfileEvents::increment(ProfileEvents::TableFunctionExecute); AccessFlags required_access = getSourceAccessType(); - String function_name = getName(); - if ((function_name != "null") && (function_name != "view") && (function_name != "viewIfPermitted")) + auto table_function_properties = TableFunctionFactory::instance().tryGetProperties(getName()); + if (!(table_function_properties && table_function_properties->allow_readonly)) required_access |= AccessType::CREATE_TEMPORARY_TABLE; context->checkAccess(required_access); diff --git a/src/TableFunctions/ITableFunction.h b/src/TableFunctions/ITableFunction.h index 4b9a87b93f1..a05edcd32c8 100644 --- a/src/TableFunctions/ITableFunction.h +++ b/src/TableFunctions/ITableFunction.h @@ -4,6 +4,7 @@ #include #include #include +#include #include #include @@ -79,6 +80,14 @@ private: virtual const char * getStorageTypeName() const = 0; }; +/// Properties of table function that are independent of argument types and parameters. +struct TableFunctionProperties +{ + Documentation documentation; + bool allow_readonly = false; +}; + + using TableFunctionPtr = std::shared_ptr; diff --git a/src/TableFunctions/TableFunctionFactory.cpp b/src/TableFunctions/TableFunctionFactory.cpp index 5ed22e39300..79802d2ec77 100644 --- a/src/TableFunctions/TableFunctionFactory.cpp +++ b/src/TableFunctions/TableFunctionFactory.cpp @@ -16,16 +16,22 @@ namespace ErrorCodes extern const int LOGICAL_ERROR; } +static const NameSet table_functions_allowed_in_readonly_mode +{ + "null", "view", "viewIfPermitted", "numbers", "numbers_mt", "generateRandom", "values", "cluster", "clusterAllReplicas" +}; void TableFunctionFactory::registerFunction( const std::string & name, TableFunctionCreator creator, Documentation doc, CaseSensitiveness case_sensitiveness) { - if (!table_functions.emplace(name, TableFunctionFactoryData{creator, doc}).second) + bool allowed_in_readonly_mode = table_functions_allowed_in_readonly_mode.contains(name); + + if (!table_functions.emplace(name, TableFunctionFactoryData{creator, {doc,allowed_in_readonly_mode}}).second) throw Exception("TableFunctionFactory: the table function name '" + name + "' is not unique", ErrorCodes::LOGICAL_ERROR); if (case_sensitiveness == CaseInsensitive - && !case_insensitive_table_functions.emplace(Poco::toLower(name), TableFunctionFactoryData{creator, doc}).second) + && !case_insensitive_table_functions.emplace(Poco::toLower(name), TableFunctionFactoryData{creator, {doc,allowed_in_readonly_mode}}).second) throw Exception("TableFunctionFactory: the case insensitive table function name '" + name + "' is not unique", ErrorCodes::LOGICAL_ERROR); } @@ -59,13 +65,13 @@ TableFunctionPtr TableFunctionFactory::tryGet( auto it = table_functions.find(name); if (table_functions.end() != it) { - res = it->second.first(); + res = it->second.creator(); } else { it = case_insensitive_table_functions.find(Poco::toLower(name)); if (case_insensitive_table_functions.end() != it) - res = it->second.first(); + res = it->second.creator(); } if (!res) @@ -86,13 +92,29 @@ bool TableFunctionFactory::isTableFunctionName(const std::string & name) const return table_functions.contains(name); } -Documentation TableFunctionFactory::getDocumentation(const std::string & name) const +std::optional TableFunctionFactory::tryGetProperties(const String & name) const { - auto it = table_functions.find(name); - if (it == table_functions.end()) - throw Exception(ErrorCodes::UNKNOWN_FUNCTION, "Unknown table function {}", name); + return tryGetPropertiesImpl(name); +} - return it->second.second; +std::optional TableFunctionFactory::tryGetPropertiesImpl(const String & name_param) const +{ + String name = getAliasToOrName(name_param); + Value found; + + /// Find by exact match. + if (auto it = table_functions.find(name); it != table_functions.end()) + { + found = it->second; + } + + if (auto jt = case_insensitive_table_functions.find(Poco::toLower(name)); jt != case_insensitive_table_functions.end()) + found = jt->second; + + if (found.creator) + return found.properties; + + return {}; } TableFunctionFactory & TableFunctionFactory::instance() diff --git a/src/TableFunctions/TableFunctionFactory.h b/src/TableFunctions/TableFunctionFactory.h index 8ff352ff9ac..24b46d40de0 100644 --- a/src/TableFunctions/TableFunctionFactory.h +++ b/src/TableFunctions/TableFunctionFactory.h @@ -3,7 +3,6 @@ #include #include #include -#include #include #include @@ -18,7 +17,24 @@ namespace DB class Context; using TableFunctionCreator = std::function; -using TableFunctionFactoryData = std::pair; + +struct TableFunctionFactoryData +{ + TableFunctionCreator creator; + TableFunctionProperties properties; + + TableFunctionFactoryData() = default; + TableFunctionFactoryData(const TableFunctionFactoryData &) = default; + TableFunctionFactoryData & operator = (const TableFunctionFactoryData &) = default; + + template + requires (!std::is_same_v) + TableFunctionFactoryData(Creator creator_, TableFunctionProperties properties_ = {}) /// NOLINT + : creator(std::forward(creator_)), properties(std::move(properties_)) + { + } +}; + /** Lets you get a table function by its name. */ @@ -48,7 +64,7 @@ public: /// Returns nullptr if not found. TableFunctionPtr tryGet(const std::string & name, ContextPtr context) const; - Documentation getDocumentation(const std::string & name) const; + std::optional tryGetProperties(const String & name) const; bool isTableFunctionName(const std::string & name) const; @@ -61,6 +77,8 @@ private: String getFactoryName() const override { return "TableFunctionFactory"; } + std::optional tryGetPropertiesImpl(const String & name) const; + TableFunctions table_functions; TableFunctions case_insensitive_table_functions; }; diff --git a/tests/queries/0_stateless/02473_functions_in_readonly_mode.reference b/tests/queries/0_stateless/02473_functions_in_readonly_mode.reference new file mode 100644 index 00000000000..4977168f515 --- /dev/null +++ b/tests/queries/0_stateless/02473_functions_in_readonly_mode.reference @@ -0,0 +1,3 @@ +0 +(123,'str') +0 diff --git a/tests/queries/0_stateless/02473_functions_in_readonly_mode.sql b/tests/queries/0_stateless/02473_functions_in_readonly_mode.sql new file mode 100644 index 00000000000..7cf55ad714c --- /dev/null +++ b/tests/queries/0_stateless/02473_functions_in_readonly_mode.sql @@ -0,0 +1,5 @@ +SELECT * from numbers(1); +select * from format(JSONEachRow, '{"x" : [123, "str"]}'); +SELECT * from numbers(1) SETTINGS readonly=1; +select * from format(JSONEachRow, '{"x" : [123, "str"]}') SETTINGS readonly=1; -- { serverError READONLY } +set readonly=0; \ No newline at end of file From 6d8e2db082bc6226afc26ea9145b503356ff011d Mon Sep 17 00:00:00 2001 From: Smita Kulkarni Date: Wed, 26 Oct 2022 18:49:14 +0200 Subject: [PATCH 111/266] Removed extra line from test - 42414 Enable table functions in readonly mode --- tests/queries/0_stateless/02473_functions_in_readonly_mode.sql | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/tests/queries/0_stateless/02473_functions_in_readonly_mode.sql b/tests/queries/0_stateless/02473_functions_in_readonly_mode.sql index 7cf55ad714c..eb2631168a8 100644 --- a/tests/queries/0_stateless/02473_functions_in_readonly_mode.sql +++ b/tests/queries/0_stateless/02473_functions_in_readonly_mode.sql @@ -1,5 +1,4 @@ SELECT * from numbers(1); select * from format(JSONEachRow, '{"x" : [123, "str"]}'); SELECT * from numbers(1) SETTINGS readonly=1; -select * from format(JSONEachRow, '{"x" : [123, "str"]}') SETTINGS readonly=1; -- { serverError READONLY } -set readonly=0; \ No newline at end of file +select * from format(JSONEachRow, '{"x" : [123, "str"]}') SETTINGS readonly=1; -- { serverError READONLY } \ No newline at end of file From 598b45f1ec2194183ec20418d7b7caf7192429be Mon Sep 17 00:00:00 2001 From: Antonio Andelic Date: Thu, 27 Oct 2022 08:06:39 +0000 Subject: [PATCH 112/266] Add test for partition only and new parts --- .../02473_optimize_old_parts.reference | 2 ++ .../0_stateless/02473_optimize_old_parts.sql | 18 ++++++++++++++++++ 2 files changed, 20 insertions(+) diff --git a/tests/queries/0_stateless/02473_optimize_old_parts.reference b/tests/queries/0_stateless/02473_optimize_old_parts.reference index e80812bddcd..9002d73ff27 100644 --- a/tests/queries/0_stateless/02473_optimize_old_parts.reference +++ b/tests/queries/0_stateless/02473_optimize_old_parts.reference @@ -8,3 +8,5 @@ With merge replicated any part range 1 With merge replicated partition only 1 +With merge partition only and new parts +3 diff --git a/tests/queries/0_stateless/02473_optimize_old_parts.sql b/tests/queries/0_stateless/02473_optimize_old_parts.sql index d673ef22f67..76c1ba73097 100644 --- a/tests/queries/0_stateless/02473_optimize_old_parts.sql +++ b/tests/queries/0_stateless/02473_optimize_old_parts.sql @@ -65,3 +65,21 @@ SELECT sleepEachRow(1) FROM numbers(6) FORMAT Null; SELECT count(*) FROM system.parts WHERE database = currentDatabase() AND table='test_replicated' AND active; DROP TABLE test_replicated; + +SELECT 'With merge partition only and new parts'; + +CREATE TABLE test_with_merge (i Int64) ENGINE = MergeTree ORDER BY i +SETTINGS min_age_to_force_merge_seconds=3, min_age_to_force_merge_on_partition_only=true; +SYSTEM STOP MERGES test_with_merge; +-- These three parts will have min_age=6 at the time of merge +INSERT INTO test_with_merge SELECT 1; +INSERT INTO test_with_merge SELECT 2; +SELECT sleepEachRow(1) FROM numbers(6) FORMAT Null; +-- These three parts will have min_age=0 at the time of merge +-- and so, nothing will be merged. +INSERT INTO test_with_merge SELECT 3; +SYSTEM START MERGES test_with_merge; + +SELECT count(*) FROM system.parts WHERE database = currentDatabase() AND table='test_with_merge' AND active; + +DROP TABLE test_with_merge; From 41dc5b30b4d1a16a4a397bc8fc034c7091adf18f Mon Sep 17 00:00:00 2001 From: Smita Kulkarni Date: Thu, 27 Oct 2022 10:52:59 +0200 Subject: [PATCH 113/266] Updated test to use TSV in format, and fixed test 02117_show_create_table_system - 42414 Enable table functions in readonly mode --- .../0_stateless/02117_show_create_table_system.reference | 3 ++- .../0_stateless/02473_functions_in_readonly_mode.reference | 2 +- .../queries/0_stateless/02473_functions_in_readonly_mode.sql | 4 ++-- 3 files changed, 5 insertions(+), 4 deletions(-) diff --git a/tests/queries/0_stateless/02117_show_create_table_system.reference b/tests/queries/0_stateless/02117_show_create_table_system.reference index ad27b86c6f5..65362e9f35d 100644 --- a/tests/queries/0_stateless/02117_show_create_table_system.reference +++ b/tests/queries/0_stateless/02117_show_create_table_system.reference @@ -1013,7 +1013,8 @@ COMMENT 'SYSTEM TABLE is built on the fly.' CREATE TABLE system.table_functions ( `name` String, - `description` String + `description` String, + `allow_readonly` UInt8 ) ENGINE = SystemTableFunctions COMMENT 'SYSTEM TABLE is built on the fly.' diff --git a/tests/queries/0_stateless/02473_functions_in_readonly_mode.reference b/tests/queries/0_stateless/02473_functions_in_readonly_mode.reference index 4977168f515..500004a06b2 100644 --- a/tests/queries/0_stateless/02473_functions_in_readonly_mode.reference +++ b/tests/queries/0_stateless/02473_functions_in_readonly_mode.reference @@ -1,3 +1,3 @@ 0 -(123,'str') +123 0 diff --git a/tests/queries/0_stateless/02473_functions_in_readonly_mode.sql b/tests/queries/0_stateless/02473_functions_in_readonly_mode.sql index eb2631168a8..c5c82d2e2bf 100644 --- a/tests/queries/0_stateless/02473_functions_in_readonly_mode.sql +++ b/tests/queries/0_stateless/02473_functions_in_readonly_mode.sql @@ -1,4 +1,4 @@ SELECT * from numbers(1); -select * from format(JSONEachRow, '{"x" : [123, "str"]}'); +SELECT * from format('TSV', '123'); SELECT * from numbers(1) SETTINGS readonly=1; -select * from format(JSONEachRow, '{"x" : [123, "str"]}') SETTINGS readonly=1; -- { serverError READONLY } \ No newline at end of file +SELECT * from format('TSV', '123') SETTINGS readonly=1; -- { serverError READONLY } \ No newline at end of file From d8e8dd006f956ef32aa496f21f4fc638c94e99a1 Mon Sep 17 00:00:00 2001 From: Yatsishin Ilya <2159081+qoega@users.noreply.github.com> Date: Thu, 27 Oct 2022 09:06:11 +0000 Subject: [PATCH 114/266] support server fail --- docker/test/sqlancer/run.sh | 32 +++++++++++++++++++------------- tests/ci/sqlancer_check.py | 15 ++++++++------- 2 files changed, 27 insertions(+), 20 deletions(-) diff --git a/docker/test/sqlancer/run.sh b/docker/test/sqlancer/run.sh index 4f73ce0359e..8c4d16b4f71 100755 --- a/docker/test/sqlancer/run.sh +++ b/docker/test/sqlancer/run.sh @@ -31,26 +31,32 @@ fi cd /workspace /clickhouse server -P /workspace/clickhouse-server.pid -L /workspace/clickhouse-server.log -E /workspace/clickhouse-server.log.err --daemon -for _ in $(seq 1 30); do if [[ $(wget -q 'localhost:8123' -O-) == 'Ok.' ]]; then break ; else sleep 1; fi ; done +for _ in $(seq 1 60); do if [[ $(wget -q 'localhost:8123' -O-) == 'Ok.' ]]; then break ; else sleep 1; fi ; done cd /sqlancer/sqlancer-master -export TIMEOUT=300 -export NUM_QUERIES=1000 -export NUM_THREADS=30 +TIMEOUT=300 +NUM_QUERIES=1000 +NUM_THREADS=10 +TESTS=( "TLPGroupBy" "TLPHaving" "TLPWhere" "TLPDistinct" "TLPAggregate" "NoREC" ) +echo ${TESTS[@]} -( java -jar target/sqlancer-*.jar --log-each-select true --print-failed false --num-threads $NUM_THREADS --timeout-seconds $TIMEOUT --num-queries $NUM_QUERIES --username default --password "" clickhouse --oracle TLPWhere | tee /workspace/TLPWhere.out ) 3>&1 1>&2 2>&3 | tee /workspace/TLPWhere.err -( java -jar target/sqlancer-*.jar --log-each-select true --print-failed false --num-threads $NUM_THREADS --timeout-seconds $TIMEOUT --num-queries $NUM_QUERIES --username default --password "" clickhouse --oracle TLPGroupBy | tee /workspace/TLPGroupBy.out ) 3>&1 1>&2 2>&3 | tee /workspace/TLPGroupBy.err -( java -jar target/sqlancer-*.jar --log-each-select true --print-failed false --num-threads $NUM_THREADS --timeout-seconds $TIMEOUT --num-queries $NUM_QUERIES --username default --password "" clickhouse --oracle TLPHaving | tee /workspace/TLPHaving.out ) 3>&1 1>&2 2>&3 | tee /workspace/TLPHaving.err -( java -jar target/sqlancer-*.jar --log-each-select true --print-failed false --num-threads $NUM_THREADS --timeout-seconds $TIMEOUT --num-queries $NUM_QUERIES --username default --password "" clickhouse --oracle TLPWhere --oracle TLPGroupBy | tee /workspace/TLPWhereGroupBy.out ) 3>&1 1>&2 2>&3 | tee /workspace/TLPWhereGroupBy.err -( java -jar target/sqlancer-*.jar --log-each-select true --print-failed false --num-threads $NUM_THREADS --timeout-seconds $TIMEOUT --num-queries $NUM_QUERIES --username default --password "" clickhouse --oracle TLPDistinct | tee /workspace/TLPDistinct.out ) 3>&1 1>&2 2>&3 | tee /workspace/TLPDistinct.err -( java -jar target/sqlancer-*.jar --log-each-select true --print-failed false --num-threads $NUM_THREADS --timeout-seconds $TIMEOUT --num-queries $NUM_QUERIES --username default --password "" clickhouse --oracle TLPAggregate | tee /workspace/TLPAggregate.out ) 3>&1 1>&2 2>&3 | tee /workspace/TLPAggregate.err -( java -jar target/sqlancer-*.jar --log-each-select true --print-failed false --num-threads $NUM_THREADS --timeout-seconds $TIMEOUT --num-queries $NUM_QUERIES --username default --password "" clickhouse --oracle TLPAggregate | tee /workspace/NoREC.out ) 3>&1 1>&2 2>&3 | tee /workspace/NoREC.err +for TEST in ${TESTS[@]}; do + echo $TEST + if [[ $(wget -q 'localhost:8123' -O-) == 'Ok.' ]] + then + echo "Server is OK" + ( java -jar target/sqlancer-*.jar --log-each-select true --print-failed false --num-threads $NUM_THREADS --timeout-seconds $TIMEOUT --num-queries $NUM_QUERIES --username default --password "" clickhouse --oracle $TEST | tee /workspace/$TEST.out ) 3>&1 1>&2 2>&3 | tee /workspace/$TEST.err + else + touch /workspace/$TEST.err /workspace/$TEST.out + echo "Server is not responding" | tee /workspace/server_crashed.log + fi +done ls /workspace -pkill -F /workspace/clickhouse-server.pid +pkill -F /workspace/clickhouse-server.pid || true -for _ in $(seq 1 30); do if [[ $(wget -q 'localhost:8123' -O-) == 'Ok.' ]]; then sleep 1 ; else break; fi ; done +for _ in $(seq 1 60); do if [[ $(wget -q 'localhost:8123' -O-) == 'Ok.' ]]; then sleep 1 ; else break; fi ; done /process_sqlancer_result.py || echo -e "failure\tCannot parse results" > /workspace/check_status.tsv ls /workspace diff --git a/tests/ci/sqlancer_check.py b/tests/ci/sqlancer_check.py index e8768982417..bdf4b534d46 100644 --- a/tests/ci/sqlancer_check.py +++ b/tests/ci/sqlancer_check.py @@ -130,7 +130,7 @@ if __name__ == "__main__": ] for t in tests: err_name = f"{t}.err" - log_name = f"{t}.err" + log_name = f"{t}.out" paths.append(os.path.join(workspace_path, err_name)) paths.append(os.path.join(workspace_path, log_name)) @@ -141,11 +141,12 @@ if __name__ == "__main__": test_results = [] # Try to get status message saved by the SQLancer try: - with open( - os.path.join(workspace_path, "status.txt"), "r", encoding="utf-8" - ) as status_f: - status = status_f.readline().rstrip("\n") - + # with open( + # os.path.join(workspace_path, "status.txt"), "r", encoding="utf-8" + # ) as status_f: + # status = status_f.readline().rstrip("\n") + if os.path.exists(os.path.join, "server_crashed.log"): + test_results.append("Server crashed", "FAIL") with open( os.path.join(workspace_path, "summary.tsv"), "r", encoding="utf-8" ) as summary_f: @@ -158,7 +159,7 @@ if __name__ == "__main__": ) as desc_f: description = desc_f.readline().rstrip("\n")[:140] except: - status = "failure" + # status = "failure" description = "Task failed: $?=" + str(retcode) report_url = upload_results( From 8a5ef517e3a2d3c2ad5ff3f2734722faa0cdbfe5 Mon Sep 17 00:00:00 2001 From: Yatsishin Ilya <2159081+qoega@users.noreply.github.com> Date: Thu, 27 Oct 2022 13:30:35 +0000 Subject: [PATCH 115/266] style --- docker/test/sqlancer/run.sh | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/docker/test/sqlancer/run.sh b/docker/test/sqlancer/run.sh index 8c4d16b4f71..8816afd6992 100755 --- a/docker/test/sqlancer/run.sh +++ b/docker/test/sqlancer/run.sh @@ -39,16 +39,16 @@ TIMEOUT=300 NUM_QUERIES=1000 NUM_THREADS=10 TESTS=( "TLPGroupBy" "TLPHaving" "TLPWhere" "TLPDistinct" "TLPAggregate" "NoREC" ) -echo ${TESTS[@]} +echo "${TESTS[@]}" -for TEST in ${TESTS[@]}; do - echo $TEST +for TEST in "${TESTS[@]}"; do + echo "$TEST" if [[ $(wget -q 'localhost:8123' -O-) == 'Ok.' ]] then echo "Server is OK" - ( java -jar target/sqlancer-*.jar --log-each-select true --print-failed false --num-threads $NUM_THREADS --timeout-seconds $TIMEOUT --num-queries $NUM_QUERIES --username default --password "" clickhouse --oracle $TEST | tee /workspace/$TEST.out ) 3>&1 1>&2 2>&3 | tee /workspace/$TEST.err + ( java -jar target/sqlancer-*.jar --log-each-select true --print-failed false --num-threads $NUM_THREADS --timeout-seconds $TIMEOUT --num-queries $NUM_QUERIES --username default --password "" clickhouse --oracle $TEST | tee "/workspace/$TEST.out" ) 3>&1 1>&2 2>&3 | tee "/workspace/$TEST.err" else - touch /workspace/$TEST.err /workspace/$TEST.out + touch "/workspace/$TEST.err" "/workspace/$TEST.out" echo "Server is not responding" | tee /workspace/server_crashed.log fi done From 3a19752d54ddee5e3c81aa28281eea45f9b02d2b Mon Sep 17 00:00:00 2001 From: Yatsishin Ilya <2159081+qoega@users.noreply.github.com> Date: Thu, 27 Oct 2022 14:08:23 +0000 Subject: [PATCH 116/266] style --- docker/test/sqlancer/run.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docker/test/sqlancer/run.sh b/docker/test/sqlancer/run.sh index 8816afd6992..4a0f0f6a512 100755 --- a/docker/test/sqlancer/run.sh +++ b/docker/test/sqlancer/run.sh @@ -46,7 +46,7 @@ for TEST in "${TESTS[@]}"; do if [[ $(wget -q 'localhost:8123' -O-) == 'Ok.' ]] then echo "Server is OK" - ( java -jar target/sqlancer-*.jar --log-each-select true --print-failed false --num-threads $NUM_THREADS --timeout-seconds $TIMEOUT --num-queries $NUM_QUERIES --username default --password "" clickhouse --oracle $TEST | tee "/workspace/$TEST.out" ) 3>&1 1>&2 2>&3 | tee "/workspace/$TEST.err" + ( java -jar target/sqlancer-*.jar --log-each-select true --print-failed false --num-threads "$NUM_THREADS" --timeout-seconds "$TIMEOUT" --num-queries "$NUM_QUERIES" --username default --password "" clickhouse --oracle "$TEST" | tee "/workspace/$TEST.out" ) 3>&1 1>&2 2>&3 | tee "/workspace/$TEST.err" else touch "/workspace/$TEST.err" "/workspace/$TEST.out" echo "Server is not responding" | tee /workspace/server_crashed.log From a4626c64af523e3efa4ad67cda5f01feb9e03b56 Mon Sep 17 00:00:00 2001 From: Yatsishin Ilya <2159081+qoega@users.noreply.github.com> Date: Thu, 27 Oct 2022 17:56:08 +0000 Subject: [PATCH 117/266] fix --- tests/ci/sqlancer_check.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/ci/sqlancer_check.py b/tests/ci/sqlancer_check.py index bdf4b534d46..415e3f2f2f7 100644 --- a/tests/ci/sqlancer_check.py +++ b/tests/ci/sqlancer_check.py @@ -145,7 +145,7 @@ if __name__ == "__main__": # os.path.join(workspace_path, "status.txt"), "r", encoding="utf-8" # ) as status_f: # status = status_f.readline().rstrip("\n") - if os.path.exists(os.path.join, "server_crashed.log"): + if os.path.exists(os.path.join(workspace_path, "server_crashed.log")): test_results.append("Server crashed", "FAIL") with open( os.path.join(workspace_path, "summary.tsv"), "r", encoding="utf-8" From c0809643915b2840a02e7962b946d05bca7619a4 Mon Sep 17 00:00:00 2001 From: Duc Canh Le Date: Fri, 28 Oct 2022 13:39:01 +0800 Subject: [PATCH 118/266] correct 'notLike' in key condition --- src/Storages/MergeTree/KeyCondition.cpp | 6 ++++-- src/Storages/MergeTree/KeyCondition.h | 2 +- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/src/Storages/MergeTree/KeyCondition.cpp b/src/Storages/MergeTree/KeyCondition.cpp index 9b579512522..9bd9f3c8853 100644 --- a/src/Storages/MergeTree/KeyCondition.cpp +++ b/src/Storages/MergeTree/KeyCondition.cpp @@ -55,7 +55,7 @@ String Range::toString() const /// Example: for `Hello\_World% ...` string it returns `Hello_World`, and for `%test%` returns an empty string. -String extractFixedPrefixFromLikePattern(const String & like_pattern) +String extractFixedPrefixFromLikePattern(const String & like_pattern, bool perfect_prefix_match) { String fixed_prefix; @@ -68,6 +68,8 @@ String extractFixedPrefixFromLikePattern(const String & like_pattern) case '%': [[fallthrough]]; case '_': + if (perfect_prefix_match && std::find_if(pos+1, end, [](const char c) { return c != '%' && c != '_'; }) != end) + return ""; return fixed_prefix; case '\\': @@ -567,7 +569,7 @@ const KeyCondition::AtomMap KeyCondition::atom_map if (value.getType() != Field::Types::String) return false; - String prefix = extractFixedPrefixFromLikePattern(value.get()); + String prefix = extractFixedPrefixFromLikePattern(value.get(), true); if (prefix.empty()) return false; diff --git a/src/Storages/MergeTree/KeyCondition.h b/src/Storages/MergeTree/KeyCondition.h index d00a25a1077..da1a74af90d 100644 --- a/src/Storages/MergeTree/KeyCondition.h +++ b/src/Storages/MergeTree/KeyCondition.h @@ -481,6 +481,6 @@ private: bool strict; }; -String extractFixedPrefixFromLikePattern(const String & like_pattern); +String extractFixedPrefixFromLikePattern(const String & like_pattern, bool perfect_prefix_match = false); } From 9c860f0e0623a19b77a8f4f474df680836406ef4 Mon Sep 17 00:00:00 2001 From: Duc Canh Le Date: Fri, 28 Oct 2022 13:42:39 +0800 Subject: [PATCH 119/266] fix test style check --- .../0_stateless/002458_key_condition_not_like_prefix.sql | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/queries/0_stateless/002458_key_condition_not_like_prefix.sql b/tests/queries/0_stateless/002458_key_condition_not_like_prefix.sql index 211fa5662e7..2c1402df27e 100644 --- a/tests/queries/0_stateless/002458_key_condition_not_like_prefix.sql +++ b/tests/queries/0_stateless/002458_key_condition_not_like_prefix.sql @@ -2,4 +2,4 @@ CREATE TABLE data (str String) ENGINE=MergeTree ORDER BY str; INSERT INTO data (str) SELECT 'aa' FROM numbers(100000); INSERT INTO data (str) SELECT 'ba' FROM numbers(100000); INSERT INTO data (str) SELECT 'ca' FROM numbers(100000); -SELECT count()FROM data WHERE str NOT LIKE 'a%' SETTINGS force_primary_key=1; \ No newline at end of file +SELECT count()FROM data WHERE str NOT LIKE 'a%' SETTINGS force_primary_key=1; From 9ea9039b0f80b22f229f98c036437814b41bfb73 Mon Sep 17 00:00:00 2001 From: Duc Canh Le Date: Fri, 28 Oct 2022 13:45:08 +0800 Subject: [PATCH 120/266] fix test name --- ...ix.reference => 02458_key_condition_not_like_prefix.reference} | 0 ...ot_like_prefix.sql => 02458_key_condition_not_like_prefix.sql} | 0 2 files changed, 0 insertions(+), 0 deletions(-) rename tests/queries/0_stateless/{002458_key_condition_not_like_prefix.reference => 02458_key_condition_not_like_prefix.reference} (100%) rename tests/queries/0_stateless/{002458_key_condition_not_like_prefix.sql => 02458_key_condition_not_like_prefix.sql} (100%) diff --git a/tests/queries/0_stateless/002458_key_condition_not_like_prefix.reference b/tests/queries/0_stateless/02458_key_condition_not_like_prefix.reference similarity index 100% rename from tests/queries/0_stateless/002458_key_condition_not_like_prefix.reference rename to tests/queries/0_stateless/02458_key_condition_not_like_prefix.reference diff --git a/tests/queries/0_stateless/002458_key_condition_not_like_prefix.sql b/tests/queries/0_stateless/02458_key_condition_not_like_prefix.sql similarity index 100% rename from tests/queries/0_stateless/002458_key_condition_not_like_prefix.sql rename to tests/queries/0_stateless/02458_key_condition_not_like_prefix.sql From 0fe0aa44d0ec318d3e9c35aa5f5af964fa28dc5e Mon Sep 17 00:00:00 2001 From: Antonio Andelic Date: Fri, 28 Oct 2022 07:38:57 +0000 Subject: [PATCH 121/266] Increase wait time --- .../queries/0_stateless/02473_optimize_old_parts.sql | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/tests/queries/0_stateless/02473_optimize_old_parts.sql b/tests/queries/0_stateless/02473_optimize_old_parts.sql index 76c1ba73097..106175ab6f5 100644 --- a/tests/queries/0_stateless/02473_optimize_old_parts.sql +++ b/tests/queries/0_stateless/02473_optimize_old_parts.sql @@ -9,7 +9,7 @@ INSERT INTO test_without_merge SELECT 1; INSERT INTO test_without_merge SELECT 2; INSERT INTO test_without_merge SELECT 3; -SELECT sleepEachRow(1) FROM numbers(6) FORMAT Null; +SELECT sleepEachRow(1) FROM numbers(9) FORMAT Null; SELECT count(*) FROM system.parts WHERE database = currentDatabase() AND table='test_without_merge' AND active; DROP TABLE test_without_merge; @@ -22,7 +22,7 @@ INSERT INTO test_with_merge SELECT 1; INSERT INTO test_with_merge SELECT 2; INSERT INTO test_with_merge SELECT 3; -SELECT sleepEachRow(1) FROM numbers(6) FORMAT Null; +SELECT sleepEachRow(1) FROM numbers(9) FORMAT Null; SELECT count(*) FROM system.parts WHERE database = currentDatabase() AND table='test_with_merge' AND active; DROP TABLE test_with_merge; @@ -35,7 +35,7 @@ INSERT INTO test_with_merge SELECT 1; INSERT INTO test_with_merge SELECT 2; INSERT INTO test_with_merge SELECT 3; -SELECT sleepEachRow(1) FROM numbers(6) FORMAT Null; +SELECT sleepEachRow(1) FROM numbers(9) FORMAT Null; SELECT count(*) FROM system.parts WHERE database = currentDatabase() AND table='test_with_merge' AND active; DROP TABLE test_with_merge; @@ -48,7 +48,7 @@ INSERT INTO test_replicated SELECT 1; INSERT INTO test_replicated SELECT 2; INSERT INTO test_replicated SELECT 3; -SELECT sleepEachRow(1) FROM numbers(6) FORMAT Null; +SELECT sleepEachRow(1) FROM numbers(9) FORMAT Null; SELECT count(*) FROM system.parts WHERE database = currentDatabase() AND table='test_replicated' AND active; DROP TABLE test_replicated; @@ -61,7 +61,7 @@ INSERT INTO test_replicated SELECT 1; INSERT INTO test_replicated SELECT 2; INSERT INTO test_replicated SELECT 3; -SELECT sleepEachRow(1) FROM numbers(6) FORMAT Null; +SELECT sleepEachRow(1) FROM numbers(9) FORMAT Null; SELECT count(*) FROM system.parts WHERE database = currentDatabase() AND table='test_replicated' AND active; DROP TABLE test_replicated; @@ -74,7 +74,7 @@ SYSTEM STOP MERGES test_with_merge; -- These three parts will have min_age=6 at the time of merge INSERT INTO test_with_merge SELECT 1; INSERT INTO test_with_merge SELECT 2; -SELECT sleepEachRow(1) FROM numbers(6) FORMAT Null; +SELECT sleepEachRow(1) FROM numbers(9) FORMAT Null; -- These three parts will have min_age=0 at the time of merge -- and so, nothing will be merged. INSERT INTO test_with_merge SELECT 3; From aa75515219579abe6db266eb26d6112811ff44a6 Mon Sep 17 00:00:00 2001 From: Yatsishin Ilya <2159081+qoega@users.noreply.github.com> Date: Fri, 28 Oct 2022 09:47:16 +0000 Subject: [PATCH 122/266] fix --- tests/ci/sqlancer_check.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/tests/ci/sqlancer_check.py b/tests/ci/sqlancer_check.py index 415e3f2f2f7..6c3ebe4d7f8 100644 --- a/tests/ci/sqlancer_check.py +++ b/tests/ci/sqlancer_check.py @@ -113,12 +113,11 @@ if __name__ == "__main__": s3_prefix = f"{pr_info.number}/{pr_info.sha}/{check_name_lower}/" tests = [ - "TLPAggregate", - "TLPDistinct", "TLPGroupBy", "TLPHaving", "TLPWhere", - "TLPWhereGroupBy", + "TLPDistinct", + "TLPAggregate", "NoREC", ] From 10873dfc9f1990ae2e8fb414a0422a1afe22baba Mon Sep 17 00:00:00 2001 From: Yatsishin Ilya <2159081+qoega@users.noreply.github.com> Date: Fri, 28 Oct 2022 11:28:20 +0000 Subject: [PATCH 123/266] succeess --- tests/ci/sqlancer_check.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/tests/ci/sqlancer_check.py b/tests/ci/sqlancer_check.py index 6c3ebe4d7f8..51c95e50746 100644 --- a/tests/ci/sqlancer_check.py +++ b/tests/ci/sqlancer_check.py @@ -136,7 +136,7 @@ if __name__ == "__main__": s3_helper = S3Helper() report_url = GITHUB_RUN_URL - status = "succeess" + status = "success" test_results = [] # Try to get status message saved by the SQLancer try: @@ -189,6 +189,5 @@ if __name__ == "__main__": ch_helper.insert_events_into(db="default", table="checks", events=prepared_events) - logging.info("Result: '%s', '%s', '%s'", status, description, report_url) - print(f"::notice ::Report url: {report_url}") + print(f"::notice Result: '{status}', '{description}', '{report_url}'") post_commit_status(gh, pr_info.sha, check_name, description, status, report_url) From e4786a611ffc8fc5426c409c4f1e8749af446a34 Mon Sep 17 00:00:00 2001 From: Antonio Andelic Date: Fri, 28 Oct 2022 11:45:18 +0000 Subject: [PATCH 124/266] Add long tag --- tests/queries/0_stateless/02473_optimize_old_parts.sql | 2 ++ 1 file changed, 2 insertions(+) diff --git a/tests/queries/0_stateless/02473_optimize_old_parts.sql b/tests/queries/0_stateless/02473_optimize_old_parts.sql index 106175ab6f5..c2bd37033c1 100644 --- a/tests/queries/0_stateless/02473_optimize_old_parts.sql +++ b/tests/queries/0_stateless/02473_optimize_old_parts.sql @@ -1,3 +1,5 @@ +-- Tags: long + DROP TABLE IF EXISTS test_without_merge; DROP TABLE IF EXISTS test_with_merge; DROP TABLE IF EXISTS test_replicated; From 1ec10ade5bf5df2271f897bafb2ffabcb11d754a Mon Sep 17 00:00:00 2001 From: DanRoscigno Date: Fri, 28 Oct 2022 09:56:16 -0400 Subject: [PATCH 125/266] add link to geo fn, fix index --- .../example-datasets/cell-towers.md | 4 +- docs/en/sql-reference/functions/geo/index.md | 113 +++++++++--------- 2 files changed, 58 insertions(+), 59 deletions(-) diff --git a/docs/en/getting-started/example-datasets/cell-towers.md b/docs/en/getting-started/example-datasets/cell-towers.md index 989ae6f0a7e..3258f186b08 100644 --- a/docs/en/getting-started/example-datasets/cell-towers.md +++ b/docs/en/getting-started/example-datasets/cell-towers.md @@ -128,13 +128,13 @@ SELECT mcc, count() FROM cell_towers GROUP BY mcc ORDER BY count() DESC LIMIT 10 10 rows in set. Elapsed: 0.019 sec. Processed 43.28 million rows, 86.55 MB (2.33 billion rows/s., 4.65 GB/s.) ``` -So, the top countries are: the USA, Germany, and Russia. +Based on the above query and the [MCC list](https://en.wikipedia.org/wiki/Mobile_country_code), the countries with the most cell towers are: the USA, Germany, and Russia. You may want to create an [External Dictionary](../../sql-reference/dictionaries/external-dictionaries/external-dicts.md) in ClickHouse to decode these values. ## Use case: Incorporate geo data {#use-case} -Using `pointInPolygon` function. +Using the [`pointInPolygon`](/docs/en/sql-reference/functions/geo/coordinates.md/#pointinpolygon) function. 1. Create a table where we will store polygons: diff --git a/docs/en/sql-reference/functions/geo/index.md b/docs/en/sql-reference/functions/geo/index.md index 64e23094105..8d659236d4c 100644 --- a/docs/en/sql-reference/functions/geo/index.md +++ b/docs/en/sql-reference/functions/geo/index.md @@ -8,70 +8,69 @@ title: "Geo Functions" ## Geographical Coordinates Functions -- [greatCircleDistance](./coordinates.md#greatCircleDistance) -- [geoDistance](./coordinates.md#geoDistance) -- [greatCircleAngle](./coordinates.md#greatCircleAngle) -- [pointInEllipses](./coordinates.md#pointInEllipses) -- [pointInPolygon](./coordinates.md#pointInPolygon) +- [greatCircleDistance](./coordinates.md#greatcircledistance) +- [geoDistance](./coordinates.md#geodistance) +- [greatCircleAngle](./coordinates.md#greatcircleangle) +- [pointInEllipses](./coordinates.md#pointinellipses) +- [pointInPolygon](./coordinates.md#pointinpolygon) ## Geohash Functions -- [geohashEncode](./geohash.md#geohashEncode) -- [geohashDecode](./geohash.md#geohashDecode) -- [geohashesInBox](./geohash.md#geohashesInBox) +- [geohashEncode](./geohash.md#geohashencode) +- [geohashDecode](./geohash.md#geohashdecode) +- [geohashesInBox](./geohash.md#geohashesinbox) ## H3 Indexes Functions -- [h3IsValid](./h3.md#h3IsValid) -- [h3GetResolution](./h3.md#h3GetResolution) -- [h3EdgeAngle](./h3.md#h3EdgeAngle) -- [h3EdgeLengthM​](./h3.md#h3EdgeLengthM​) -- [h3EdgeLengthKm](./h3.md#h3EdgeLengthKm) -- [geoToH3](./h3.md#geoToH3) -- [h3ToGeo](./h3.md#h3ToGeo) -- [h3ToGeoBoundary](./h3.md#h3ToGeoBoundary) -- [h3kRing](./h3.md#h3kRing) -- [h3GetBaseCell](./h3.md#h3GetBaseCell) -- [h3HexAreaM2](./h3.md#h3HexAreaM2) -- [h3HexAreaKm2](./h3.md#h3HexAreaKm2) -- [h3IndexesAreNeighbors](./h3.md#h3IndexesAreNeighbors) -- [h3ToChildren](./h3.md#h3ToChildren) -- [h3ToParent](./h3.md#h3ToParent) -- [h3ToString](./h3.md#h3ToString) -- [stringToH3](./h3.md#stringToH3) -- [h3GetResolution](./h3.md#h3GetResolution) -- [h3IsResClassIII](./h3.md#h3IsResClassIII) -- [h3IsPentagon](./h3.md#h3IsPentagon) -- [h3GetFaces](./h3.md#h3GetFaces) -- [h3CellAreaM2](./h3.md#h3CellAreaM2) -- [h3CellAreaRads2](./h3.md#h3CellAreaRads2) -- [h3ToCenterChild](./h3.md#h3ToCenterChild) -- [h3ExactEdgeLengthM](./h3.md#h3ExactEdgeLengthM) -- [h3ExactEdgeLengthKm](./h3.md#h3ExactEdgeLengthKm) -- [h3ExactEdgeLengthRads](./h3.md#h3ExactEdgeLengthRads) -- [h3NumHexagons](./h3.md#h3NumHexagons) -- [h3Line](./h3.md#h3Line) -- [h3Distance](./h3.md#h3Distance) -- [h3HexRing](./h3.md#h3HexRing) -- [h3GetUnidirectionalEdge](./h3.md#h3GetUnidirectionalEdge) -- [h3UnidirectionalEdgeIsValid](./h3.md#h3UnidirectionalEdgeIsValid) -- [h3GetOriginIndexFromUnidirectionalEdge](./h3.md#h3GetOriginIndexFromUnidirectionalEdge) -- [h3GetDestinationIndexFromUnidirectionalEdge](./h3.md#h3GetDestinationIndexFromUnidirectionalEdge) -- [h3GetIndexesFromUnidirectionalEdge](./h3.md#h3GetIndexesFromUnidirectionalEdge) -- [h3GetUnidirectionalEdgesFromHexagon](./h3.md#h3GetUnidirectionalEdgesFromHexagon) -- [h3GetUnidirectionalEdgeBoundary](./h3.md#h3GetUnidirectionalEdgeBoundary) +- [h3IsValid](./h3.md#h3isvalid) +- [h3GetResolution](./h3.md#h3getresolution) +- [h3EdgeAngle](./h3.md#h3edgeangle) +- [h3EdgeLengthM](./h3.md#h3edgelengthm) +- [h3EdgeLengthKm](./h3.md#h3edgelengthkm) +- [geoToH3](./h3.md#geotoh3) +- [h3ToGeo](./h3.md#h3togeo) +- [h3ToGeoBoundary](./h3.md#h3togeoboundary) +- [h3kRing](./h3.md#h3kring) +- [h3GetBaseCell](./h3.md#h3getbasecell) +- [h3HexAreaM2](./h3.md#h3hexaream2) +- [h3HexAreaKm2](./h3.md#h3hexareakm2) +- [h3IndexesAreNeighbors](./h3.md#h3indexesareneighbors) +- [h3ToChildren](./h3.md#h3tochildren) +- [h3ToParent](./h3.md#h3toparent) +- [h3ToString](./h3.md#h3tostring) +- [stringToH3](./h3.md#stringtoh3) +- [h3GetResolution](./h3.md#h3getresolution) +- [h3IsResClassIII](./h3.md#h3isresclassiii) +- [h3IsPentagon](./h3.md#h3ispentagon) +- [h3GetFaces](./h3.md#h3getfaces) +- [h3CellAreaM2](./h3.md#h3cellaream2) +- [h3CellAreaRads2](./h3.md#h3cellarearads2) +- [h3ToCenterChild](./h3.md#h3tocenterchild) +- [h3ExactEdgeLengthM](./h3.md#h3exactedgelengthm) +- [h3ExactEdgeLengthKm](./h3.md#h3exactedgelengthkm) +- [h3ExactEdgeLengthRads](./h3.md#h3exactedgelengthrads) +- [h3NumHexagons](./h3.md#h3numhexagons) +- [h3Line](./h3.md#h3line) +- [h3Distance](./h3.md#h3distance) +- [h3HexRing](./h3.md#h3hexring) +- [h3GetUnidirectionalEdge](./h3.md#h3getunidirectionaledge) +- [h3UnidirectionalEdgeIsValid](./h3.md#h3unidirectionaledgeisvalid) +- [h3GetOriginIndexFromUnidirectionalEdge](./h3.md#h3getoriginindexfromunidirectionaledge) +- [h3GetDestinationIndexFromUnidirectionalEdge](./h3.md#h3getdestinationindexfromunidirectionaledge) +- [h3GetIndexesFromUnidirectionalEdge](./h3.md#h3getindexesfromunidirectionaledge) +- [h3GetUnidirectionalEdgesFromHexagon](./h3.md#h3getunidirectionaledgesfromhexagon) +- [h3GetUnidirectionalEdgeBoundary](./h3.md#h3getunidirectionaledgeboundary) ## S2 Index Functions -- [geoToS2](./s2.md#geoToS2) -- [s2ToGeo](./s2.md#s2ToGeo) -- [s2GetNeighbors](./s2.md#s2GetNeighbors) -- [s2CellsIntersect](./s2.md#s2CellsIntersect) -- [s2CapContains](./s2.md#s2CapContains) -- [s2CapUnion](./s2.md#s2CapUnion) -- [s2RectAdd](./s2.md#s2RectAdd) -- [s2RectContains](./s2.md#s2RectContains) -- [s2RectUinion](./s2.md#s2RectUinion) -- [s2RectIntersection](./s2.md#s2RectIntersection) +- [geoToS2](./s2.md#geotos2) +- [s2ToGeo](./s2.md#s2togeo) +- [s2GetNeighbors](./s2.md#s2getneighbors) +- [s2CellsIntersect](./s2.md#s2cellsintersect) +- [s2CapContains](./s2.md#s2capcontains) +- [s2CapUnion](./s2.md#s2capunion) +- [s2RectAdd](./s2.md#s2rectadd) +- [s2RectContains](./s2.md#s2rectcontains) +- [s2RectUnion](./s2.md#s2rectunion) +- [s2RectIntersection](./s2.md#s2rectintersection) -[Original article](https://clickhouse.com/docs/en/sql-reference/functions/geo/) From 418539cd514fbf34c3ce5436e76bc9d81bbe1298 Mon Sep 17 00:00:00 2001 From: DanRoscigno Date: Fri, 28 Oct 2022 11:54:44 -0400 Subject: [PATCH 126/266] add images --- .../example-datasets/cell-towers.md | 48 ++++++++++++++++-- .../images/superset-cell-tower-dashboard.png | Bin 0 -> 486355 bytes .../images/superset-connect-a-database.png | Bin 0 -> 74606 bytes .../images/superset-create-map.png | Bin 0 -> 294614 bytes .../images/superset-lon-lat.png | Bin 0 -> 38436 bytes .../images/superset-mcc-204.png | Bin 0 -> 12467 bytes .../images/superset-radio-umts.png | Bin 0 -> 12698 bytes .../images/superset-umts-netherlands.png | Bin 0 -> 46590 bytes 8 files changed, 45 insertions(+), 3 deletions(-) create mode 100644 docs/en/getting-started/example-datasets/images/superset-cell-tower-dashboard.png create mode 100644 docs/en/getting-started/example-datasets/images/superset-connect-a-database.png create mode 100644 docs/en/getting-started/example-datasets/images/superset-create-map.png create mode 100644 docs/en/getting-started/example-datasets/images/superset-lon-lat.png create mode 100644 docs/en/getting-started/example-datasets/images/superset-mcc-204.png create mode 100644 docs/en/getting-started/example-datasets/images/superset-radio-umts.png create mode 100644 docs/en/getting-started/example-datasets/images/superset-umts-netherlands.png diff --git a/docs/en/getting-started/example-datasets/cell-towers.md b/docs/en/getting-started/example-datasets/cell-towers.md index 3258f186b08..c66b49d4751 100644 --- a/docs/en/getting-started/example-datasets/cell-towers.md +++ b/docs/en/getting-started/example-datasets/cell-towers.md @@ -12,15 +12,27 @@ import ActionsMenu from '@site/docs/en/_snippets/_service_actions_menu.md'; import SQLConsoleDetail from '@site/docs/en/_snippets/_launch_sql_console.md'; import SupersetDocker from '@site/docs/en/_snippets/_add_superset_detail.md'; +## Goal + +In this guide you will learn how to: +- load the OpenCellID data in Clickhouse +- connect Apache Superset to ClickHouse +- build a dashboard based on data available in the dataset +- how to choose data types for your table fields +- how to choose a primary key + +Here is a preview of the dashboard created in this guide: + +![Dashboard of cell towers by radio type in mcc 204](@site/docs/en/getting-started/example-datasets/images/superset-cell-tower-dashboard.png) + +## Get the Dataset {#get-the-dataset} + This dataset is from [OpenCellid](https://www.opencellid.org/) - The world's largest Open Database of Cell Towers. As of 2021, it contains more than 40 million records about cell towers (GSM, LTE, UMTS, etc.) around the world with their geographical coordinates and metadata (country code, network, etc). OpenCelliD Project is licensed under a Creative Commons Attribution-ShareAlike 4.0 International License, and we redistribute a snapshot of this dataset under the terms of the same license. The up-to-date version of the dataset is available to download after sign in. - -## Get the Dataset {#get-the-dataset} - @@ -248,6 +260,36 @@ Superset is easy to run from Docker. If you already have Superset running, all +## Build a dashboard + +1. foo + +![Dashboard of cell towers by radio type in mcc 204](@site/docs/en/getting-started/example-datasets/images/superset-cell-tower-dashboard.png) + +1. foo + +![Add ClickHouse as a Superset datasource](@site/docs/en/getting-started/example-datasets/images/superset-connect-a-database.png) + +1. foo + +![Create a map in Superset](@site/docs/en/getting-started/example-datasets/images/superset-create-map.png) + +1. foo + +![Specify longitude and latitude fields](@site/docs/en/getting-started/example-datasets/images/superset-lon-lat.png) + +1. foo + +![Filter on MCC 204](@site/docs/en/getting-started/example-datasets/images/superset-mcc-204.png) + +1. foo + +![Filter on radio = UMTS](@site/docs/en/getting-started/example-datasets/images/superset-radio-umts.png) + +1. foo + +![Chart for UMTS radios in MCC 204](@site/docs/en/getting-started/example-datasets/images/superset-umts-netherlands.png) + The data is also available for interactive queries in the [Playground](https://play.clickhouse.com/play?user=play), [example](https://play.clickhouse.com/play?user=play#U0VMRUNUIG1jYywgY291bnQoKSBGUk9NIGNlbGxfdG93ZXJzIEdST1VQIEJZIG1jYyBPUkRFUiBCWSBjb3VudCgpIERFU0M=). Although you cannot create temporary tables there. diff --git a/docs/en/getting-started/example-datasets/images/superset-cell-tower-dashboard.png b/docs/en/getting-started/example-datasets/images/superset-cell-tower-dashboard.png new file mode 100644 index 0000000000000000000000000000000000000000..8197ea223c278f314fd9b7b17b95d15e9373a92f GIT binary patch literal 486355 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