mirror of
https://github.com/ClickHouse/ClickHouse.git
synced 2024-11-26 09:32:01 +00:00
dbms: SAMPLE ... OFFSET development: using rational numbers [#METR-18847].
This commit is contained in:
parent
f0a17a377a
commit
2d00e5d84f
@ -60,7 +60,4 @@ template <> struct TypeName<Float32> { static std::string get() { return "Float
|
||||
template <> struct TypeName<Float64> { static std::string get() { return "Float64"; } };
|
||||
template <> struct TypeName<String> { static std::string get() { return "String"; } };
|
||||
|
||||
/// Эти типы не поддерживаются СУБД. Но используются в других местах.
|
||||
template <> struct TypeName<long double>{ static std::string get() { return "long double"; } };
|
||||
|
||||
}
|
||||
|
@ -86,9 +86,6 @@ struct Settings
|
||||
M(SettingTotalsMode, totals_mode, TotalsMode::AFTER_HAVING_EXCLUSIVE) \
|
||||
M(SettingFloat, totals_auto_threshold, 0.5) \
|
||||
\
|
||||
/** Сэмплирование по умолчанию. Если равно 1, то отключено. */ \
|
||||
M(SettingFloat, default_sample, 1.0) \
|
||||
\
|
||||
/** Включена ли компиляция запросов. */ \
|
||||
M(SettingBool, compile, false) \
|
||||
/** Количество одинаковых по структуре запросов перед тем, как инициируется их компиляция. */ \
|
||||
|
43
dbms/include/DB/Parsers/ASTSampleRatio.h
Normal file
43
dbms/include/DB/Parsers/ASTSampleRatio.h
Normal file
@ -0,0 +1,43 @@
|
||||
#pragma once
|
||||
|
||||
#include <DB/Parsers/IAST.h>
|
||||
|
||||
|
||||
namespace DB
|
||||
{
|
||||
|
||||
/** Коэффициент сэмплирования вида 0.1 или 1/10.
|
||||
* Важно сохранять его как рациональное число без преобразования в IEEE-754.
|
||||
*/
|
||||
class ASTSampleRatio : public IAST
|
||||
{
|
||||
public:
|
||||
using BigNum = __uint128_t; /// Должен вмещать в себя результат перемножения двух UInt64.
|
||||
|
||||
struct Rational
|
||||
{
|
||||
BigNum numerator = 0;
|
||||
BigNum denominator = 1;
|
||||
};
|
||||
|
||||
Rational ratio;
|
||||
|
||||
ASTSampleRatio() = default;
|
||||
ASTSampleRatio(const StringRange range_) : IAST(range_) {}
|
||||
ASTSampleRatio(const StringRange range_, Rational & ratio_) : IAST(range_), ratio(ratio_) {}
|
||||
|
||||
String getID() const override { return "SampleRatio_" + toString(ratio); }
|
||||
|
||||
ASTPtr clone() const override { return new ASTSampleRatio(*this); }
|
||||
|
||||
static String toString(BigNum num);
|
||||
static String toString(Rational ratio);
|
||||
|
||||
protected:
|
||||
void formatImpl(const FormatSettings & settings, FormatState & state, FormatStateStacked frame) const override
|
||||
{
|
||||
settings.ostr << toString(ratio);
|
||||
}
|
||||
};
|
||||
|
||||
}
|
19
dbms/include/DB/Parsers/ParserSampleRatio.h
Normal file
19
dbms/include/DB/Parsers/ParserSampleRatio.h
Normal file
@ -0,0 +1,19 @@
|
||||
#pragma once
|
||||
|
||||
#include <DB/Parsers/IParserBase.h>
|
||||
|
||||
|
||||
namespace DB
|
||||
{
|
||||
|
||||
/** Коэффициент сэмплирования вида 0.1 или 1/10.
|
||||
* Парсится как рациональное число без преобразования в IEEE-754.
|
||||
*/
|
||||
class ParserSampleRatio : public IParserBase
|
||||
{
|
||||
protected:
|
||||
const char * getName() const { return "Sample ratio or offset"; }
|
||||
bool parseImpl(Pos & pos, Pos end, ASTPtr & node, Pos & max_parsed_pos, Expected & expected);
|
||||
};
|
||||
|
||||
}
|
@ -698,12 +698,6 @@ QueryProcessingStage::Enum InterpreterSelectQuery::executeFetchColumns()
|
||||
interpreter_subquery->ignoreWithTotals();
|
||||
}
|
||||
|
||||
/// если в настройках установлен default_sample != 1, то все запросы выполняем с сэмплингом
|
||||
/// если таблица не поддерживает сэмплинг получим исключение
|
||||
/// поэтому запросы типа SHOW TABLES работать с включенном default_sample не будут
|
||||
if (!query.sample_size && settings.default_sample != 1)
|
||||
query.sample_size = new ASTLiteral(StringRange(), Float64(settings.default_sample));
|
||||
|
||||
if (query.sample_size && (!storage || !storage->supportsSampling()))
|
||||
throw Exception("Illegal SAMPLE: table doesn't support sampling", ErrorCodes::SAMPLING_NOT_SUPPORTED);
|
||||
|
||||
|
40
dbms/src/Parsers/ASTSampleRatio.cpp
Normal file
40
dbms/src/Parsers/ASTSampleRatio.cpp
Normal file
@ -0,0 +1,40 @@
|
||||
#include <DB/Parsers/ASTSampleRatio.h>
|
||||
|
||||
namespace DB
|
||||
{
|
||||
|
||||
|
||||
String ASTSampleRatio::toString(BigNum num)
|
||||
{
|
||||
if (num == 0)
|
||||
return "0";
|
||||
|
||||
static const size_t MAX_WIDTH = 40;
|
||||
|
||||
char tmp[MAX_WIDTH];
|
||||
|
||||
char * pos;
|
||||
for (pos = tmp + MAX_WIDTH - 1; num != 0; --pos)
|
||||
{
|
||||
*pos = '0' + num % 10;
|
||||
num /= 10;
|
||||
}
|
||||
|
||||
++pos;
|
||||
|
||||
return String(pos, tmp + MAX_WIDTH - pos);
|
||||
}
|
||||
|
||||
|
||||
String ASTSampleRatio::toString(Rational ratio)
|
||||
{
|
||||
if (ratio.denominator == 1)
|
||||
return toString(ratio.numerator);
|
||||
else
|
||||
return toString(ratio.numerator) + " / " + toString(ratio.denominator);
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
}
|
128
dbms/src/Parsers/ParserSampleRatio.cpp
Normal file
128
dbms/src/Parsers/ParserSampleRatio.cpp
Normal file
@ -0,0 +1,128 @@
|
||||
#include <DB/Parsers/CommonParsers.h>
|
||||
#include <DB/Parsers/ParserSampleRatio.h>
|
||||
#include <DB/Parsers/ASTSampleRatio.h>
|
||||
#include <DB/IO/ReadHelpers.h>
|
||||
|
||||
|
||||
namespace DB
|
||||
{
|
||||
|
||||
|
||||
static bool parseDecimal(IParser::Pos & pos, IParser::Pos end, ASTSampleRatio::Rational & res, IParser::Pos & max_parsed_pos)
|
||||
{
|
||||
ParserWhiteSpaceOrComments ws;
|
||||
ws.ignore(pos, end);
|
||||
|
||||
UInt64 num_before = 0;
|
||||
UInt64 num_after = 0;
|
||||
Int64 exponent = 0;
|
||||
|
||||
IParser::Pos pos_after_first_num = tryReadIntText(num_before, pos, end);
|
||||
|
||||
bool has_num_before_point = pos_after_first_num > pos;
|
||||
pos = pos_after_first_num;
|
||||
bool has_point = pos < end && *pos == '.';
|
||||
|
||||
if (has_point)
|
||||
++pos;
|
||||
|
||||
if (!has_num_before_point && !has_point)
|
||||
return false;
|
||||
|
||||
size_t number_of_digits_after_point = 0;
|
||||
|
||||
if (has_point)
|
||||
{
|
||||
IParser::Pos pos_after_second_num = tryReadIntText(num_after, pos, end);
|
||||
number_of_digits_after_point = pos_after_second_num - pos;
|
||||
pos = pos_after_second_num;
|
||||
}
|
||||
|
||||
bool has_exponent = pos < end && (*pos == 'e' || *pos == 'E');
|
||||
|
||||
if (has_exponent)
|
||||
{
|
||||
++pos;
|
||||
IParser::Pos pos_after_exponent = tryReadIntText(exponent, pos, end);
|
||||
|
||||
if (pos_after_exponent == pos)
|
||||
return false;
|
||||
|
||||
pos = pos_after_exponent;
|
||||
}
|
||||
|
||||
res.numerator = num_before * exp10(number_of_digits_after_point) + num_after;
|
||||
res.denominator = exp10(number_of_digits_after_point);
|
||||
|
||||
if (exponent > 0)
|
||||
res.numerator *= exp10(exponent);
|
||||
if (exponent < 0)
|
||||
res.denominator *= exp10(-exponent);
|
||||
|
||||
/// NOTE Удаление общих степеней десяти из числителя и знаменателя - не нужно.
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
/** Возможные варианты:
|
||||
*
|
||||
* 12345
|
||||
* - целое число
|
||||
*
|
||||
* 0.12345
|
||||
* .12345
|
||||
* 0.
|
||||
* - дробь в обычной десятичной записи
|
||||
*
|
||||
* 1.23e-1
|
||||
* - дробь в инженерной десятичной записи
|
||||
*
|
||||
* 123 / 456
|
||||
* - дробь с произвольным знаменателем
|
||||
*
|
||||
* На всякий случай, в числителе и знаменателе дроби, поддерживаем предыдущие случаи.
|
||||
* Пример:
|
||||
* 123.0 / 456e0
|
||||
*/
|
||||
bool ParserSampleRatio::parseImpl(IParser::Pos & pos, IParser::Pos end, ASTPtr & node, IParser::Pos & max_parsed_pos, Expected & expected)
|
||||
{
|
||||
auto begin = pos;
|
||||
|
||||
ParserWhiteSpaceOrComments ws;
|
||||
|
||||
ASTSampleRatio::Rational numerator;
|
||||
ASTSampleRatio::Rational denominator;
|
||||
ASTSampleRatio::Rational res;
|
||||
|
||||
ws.ignore(pos, end);
|
||||
|
||||
if (!parseDecimal(pos, end, numerator, max_parsed_pos))
|
||||
return false;
|
||||
|
||||
ws.ignore(pos, end);
|
||||
|
||||
bool has_slash = pos < end && *pos == '/';
|
||||
|
||||
if (has_slash)
|
||||
{
|
||||
++pos;
|
||||
ws.ignore(pos, end);
|
||||
|
||||
if (!parseDecimal(pos, end, denominator, max_parsed_pos))
|
||||
return false;
|
||||
|
||||
res.numerator = numerator.numerator * denominator.denominator;
|
||||
res.denominator = numerator.denominator * denominator.numerator;
|
||||
}
|
||||
else
|
||||
{
|
||||
res = numerator;
|
||||
}
|
||||
|
||||
ws.ignore(pos, end);
|
||||
|
||||
node = new ASTSampleRatio(StringRange(begin, pos), res);
|
||||
return true;
|
||||
}
|
||||
|
||||
}
|
@ -6,6 +6,7 @@
|
||||
#include <DB/Parsers/ExpressionListParsers.h>
|
||||
#include <DB/Parsers/ParserJoin.h>
|
||||
#include <DB/Parsers/ParserSetQuery.h>
|
||||
#include <DB/Parsers/ParserSampleRatio.h>
|
||||
#include <DB/Parsers/ParserSelectQuery.h>
|
||||
|
||||
namespace DB
|
||||
@ -156,9 +157,9 @@ bool ParserSelectQuery::parseImpl(Pos & pos, Pos end, ASTPtr & node, Pos & max_p
|
||||
{
|
||||
ws.ignore(pos, end);
|
||||
|
||||
ParserNumber num;
|
||||
ParserSampleRatio ratio;
|
||||
|
||||
if (!num.parse(pos, end, select_query->sample_size, max_parsed_pos, expected))
|
||||
if (!ratio.parse(pos, end, select_query->sample_size, max_parsed_pos, expected))
|
||||
return false;
|
||||
|
||||
ws.ignore(pos, end);
|
||||
@ -168,9 +169,7 @@ bool ParserSelectQuery::parseImpl(Pos & pos, Pos end, ASTPtr & node, Pos & max_p
|
||||
{
|
||||
ws.ignore(pos, end);
|
||||
|
||||
ParserNumber num;
|
||||
|
||||
if (!num.parse(pos, end, select_query->sample_offset, max_parsed_pos, expected))
|
||||
if (!ratio.parse(pos, end, select_query->sample_offset, max_parsed_pos, expected))
|
||||
return false;
|
||||
|
||||
ws.ignore(pos, end);
|
||||
|
@ -210,7 +210,7 @@ public:
|
||||
|
||||
const MergeTreeSettings & settings = context.getMergeTreeSettings();
|
||||
|
||||
bool ok = true;
|
||||
bool ok = /*true*/false;
|
||||
std::stringstream message;
|
||||
|
||||
for (const auto & db : replicated_tables)
|
||||
|
@ -1,9 +1,12 @@
|
||||
#include <boost/rational.hpp> /// Для вычислений, связанных с коэффициентами сэмплирования.
|
||||
|
||||
#include <DB/Core/FieldVisitors.h>
|
||||
#include <DB/Storages/MergeTree/MergeTreeDataSelectExecutor.h>
|
||||
#include <DB/Storages/MergeTree/MergeTreeBlockInputStream.h>
|
||||
#include <DB/Storages/MergeTree/MergeTreeReadPool.h>
|
||||
#include <DB/Storages/MergeTree/MergeTreeThreadBlockInputStream.h>
|
||||
#include <DB/Parsers/ASTIdentifier.h>
|
||||
#include <DB/Parsers/ASTSampleRatio.h>
|
||||
#include <DB/DataStreams/ExpressionBlockInputStream.h>
|
||||
#include <DB/DataStreams/FilterBlockInputStream.h>
|
||||
#include <DB/DataStreams/CollapsingFinalBlockInputStream.h>
|
||||
@ -65,10 +68,13 @@ size_t MergeTreeDataSelectExecutor::getApproximateTotalRowsToRead(
|
||||
}
|
||||
|
||||
|
||||
/** Пожалуй, наиболее удобный способ разбить диапазон 64-битных целых чисел на интервалы по их относительной величине
|
||||
* - использовать для этого long double. Это некроссплатформенно. Надо, чтобы long double содержал хотя бы 64 бита мантиссы.
|
||||
*/
|
||||
using RelativeSize = long double;
|
||||
using RelativeSize = boost::rational<ASTSampleRatio::BigNum>;
|
||||
|
||||
static std::ostream & operator<<(std::ostream & ostr, const RelativeSize & x)
|
||||
{
|
||||
ostr << ASTSampleRatio::toString(x.numerator()) << "/" << ASTSampleRatio::toString(x.denominator());
|
||||
return ostr;
|
||||
}
|
||||
|
||||
|
||||
/// Переводит размер сэмпла в приблизительном количестве строк (вида SAMPLE 1000000) в относительную величину (вида SAMPLE 0.1).
|
||||
@ -77,8 +83,10 @@ static RelativeSize convertAbsoluteSampleSizeToRelative(const ASTPtr & node, siz
|
||||
if (approx_total_rows == 0)
|
||||
return 1;
|
||||
|
||||
size_t absolute_sample_size = apply_visitor(FieldVisitorConvertToNumber<UInt64>(), typeid_cast<const ASTLiteral &>(*node).value);
|
||||
return std::min(RelativeSize(1.0), RelativeSize(absolute_sample_size) / approx_total_rows);
|
||||
const ASTSampleRatio & node_sample = typeid_cast<const ASTSampleRatio &>(*node);
|
||||
|
||||
auto absolute_sample_size = node_sample.ratio.numerator / node_sample.ratio.denominator;
|
||||
return std::min(RelativeSize(1), RelativeSize(absolute_sample_size) / approx_total_rows);
|
||||
}
|
||||
|
||||
|
||||
@ -166,16 +174,18 @@ BlockInputStreams MergeTreeDataSelectExecutor::read(
|
||||
|
||||
if (select.sample_size)
|
||||
{
|
||||
relative_sample_size = apply_visitor(FieldVisitorConvertToNumber<RelativeSize>(),
|
||||
typeid_cast<ASTLiteral&>(*select.sample_size).value);
|
||||
relative_sample_size.assign(
|
||||
typeid_cast<const ASTSampleRatio &>(*select.sample_size).ratio.numerator,
|
||||
typeid_cast<const ASTSampleRatio &>(*select.sample_size).ratio.denominator);
|
||||
|
||||
if (relative_sample_size < 0)
|
||||
throw Exception("Negative sample size", ErrorCodes::ARGUMENT_OUT_OF_BOUND);
|
||||
|
||||
relative_sample_offset = 0;
|
||||
if (select.sample_offset)
|
||||
relative_sample_offset = apply_visitor(FieldVisitorConvertToNumber<RelativeSize>(),
|
||||
typeid_cast<ASTLiteral&>(*select.sample_offset).value);
|
||||
relative_sample_offset.assign(
|
||||
typeid_cast<const ASTSampleRatio &>(*select.sample_offset).ratio.numerator,
|
||||
typeid_cast<const ASTSampleRatio &>(*select.sample_offset).ratio.denominator);
|
||||
|
||||
if (relative_sample_offset < 0)
|
||||
throw Exception("Negative sample offset", ErrorCodes::ARGUMENT_OUT_OF_BOUND);
|
||||
@ -234,10 +244,8 @@ BlockInputStreams MergeTreeDataSelectExecutor::read(
|
||||
* <------> - size
|
||||
* <--><--> - кусочки для разных parallel_replica_offset, выбираем второй.
|
||||
*
|
||||
* TODO
|
||||
* Очень важно, чтобы интервалы для разных parallel_replica_offset покрывали весь диапазон без пропусков и перекрытий.
|
||||
* Также важно, чтобы весь юнивёрсум можно было покрыть, используя SAMPLE 0.1 OFFSET 0, ... OFFSET 0.9 и похожие десятичные дроби.
|
||||
* Сейчас это не гарантируется.
|
||||
*/
|
||||
|
||||
bool use_sampling = relative_sample_size > 0 || settings.parallel_replicas_count > 1;
|
||||
@ -272,16 +280,16 @@ BlockInputStreams MergeTreeDataSelectExecutor::read(
|
||||
bool has_lower_limit = false;
|
||||
bool has_upper_limit = false;
|
||||
|
||||
RelativeSize lower_limit_float = relative_sample_offset * size_of_universum;
|
||||
RelativeSize upper_limit_float = (relative_sample_offset + relative_sample_size) * size_of_universum;
|
||||
RelativeSize lower_limit_rational = relative_sample_offset * size_of_universum;
|
||||
RelativeSize upper_limit_rational = (relative_sample_offset + relative_sample_size) * size_of_universum;
|
||||
|
||||
UInt64 lower = lower_limit_float;
|
||||
UInt64 upper = upper_limit_float;
|
||||
UInt64 lower = boost::rational_cast<UInt64>(lower_limit_rational);
|
||||
UInt64 upper = boost::rational_cast<UInt64>(upper_limit_rational);
|
||||
|
||||
if (lower > 0)
|
||||
has_lower_limit = true;
|
||||
|
||||
if (upper_limit_float <= size_of_universum)
|
||||
if (lower_limit_rational <= size_of_universum)
|
||||
has_upper_limit = true;
|
||||
|
||||
/* std::cerr << std::fixed << std::setprecision(100)
|
||||
|
Loading…
Reference in New Issue
Block a user