dbms: SAMPLE ... OFFSET development: using rational numbers [#METR-18847].

This commit is contained in:
Alexey Milovidov 2015-11-20 00:34:53 +03:00
parent f0a17a377a
commit 2d00e5d84f
10 changed files with 260 additions and 35 deletions

View File

@ -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"; } };
}

View File

@ -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) \
/** Количество одинаковых по структуре запросов перед тем, как инициируется их компиляция. */ \

View 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);
}
};
}

View 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);
};
}

View File

@ -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);

View 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);
}
}

View 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;
}
}

View File

@ -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);

View File

@ -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)

View File

@ -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)