mirror of
https://github.com/ClickHouse/ClickHouse.git
synced 2024-12-19 04:42:37 +00:00
Add compat setting for sane NULL behavior in functions 'least' and 'greatest'
This commit is contained in:
parent
c79cb46603
commit
e80082c24f
@ -3147,16 +3147,19 @@ Possible values:
|
||||
Allow execute multiIf function columnar
|
||||
)", 0) \
|
||||
DECLARE(Bool, formatdatetime_f_prints_single_zero, false, R"(
|
||||
Formatter '%f' in function 'formatDateTime()' prints a single zero instead of six zeros if the formatted value has no fractional seconds.
|
||||
Formatter '%f' in function 'formatDateTime' prints a single zero instead of six zeros if the formatted value has no fractional seconds.
|
||||
)", 0) \
|
||||
DECLARE(Bool, formatdatetime_parsedatetime_m_is_month_name, true, R"(
|
||||
Formatter '%M' in functions 'formatDateTime()' and 'parseDateTime()' print/parse the month name instead of minutes.
|
||||
Formatter '%M' in functions 'formatDateTime' and 'parseDateTime' print/parse the month name instead of minutes.
|
||||
)", 0) \
|
||||
DECLARE(Bool, parsedatetime_parse_without_leading_zeros, true, R"(
|
||||
Formatters '%c', '%l' and '%k' in function 'parseDateTime()' parse months and hours without leading zeros.
|
||||
Formatters '%c', '%l' and '%k' in function 'parseDateTime' parse months and hours without leading zeros.
|
||||
)", 0) \
|
||||
DECLARE(Bool, formatdatetime_format_without_leading_zeros, false, R"(
|
||||
Formatters '%c', '%l' and '%k' in function 'formatDateTime()' print months and hours without leading zeros.
|
||||
Formatters '%c', '%l' and '%k' in function 'formatDateTime' print months and hours without leading zeros.
|
||||
)", 0) \
|
||||
DECLARE(Bool, least_greatest_legacy_null_behavior, false, R"(
|
||||
If enabled, functions 'least' and 'greatest' return NULL if one of their arguments is NULL.
|
||||
)", 0) \
|
||||
\
|
||||
DECLARE(UInt64, max_partitions_per_insert_block, 100, R"(
|
||||
|
@ -79,6 +79,7 @@ static std::initializer_list<std::pair<ClickHouseVersion, SettingsChangesHistory
|
||||
{"http_response_headers", "", "", "New setting."},
|
||||
{"skip_redundant_aliases_in_udf", false, false, "New setting."},
|
||||
{"parallel_replicas_index_analysis_only_on_coordinator", true, true, "Index analysis done only on replica-coordinator and skipped on other replicas. Effective only with enabled parallel_replicas_local_plan"}, // enabling it was moved to 24.10
|
||||
{"least_greatest_legacy_null_behavior", false, false, "New setting"},
|
||||
/// Release closed. Please use 25.1
|
||||
}
|
||||
},
|
||||
|
@ -2,8 +2,10 @@
|
||||
|
||||
#include <DataTypes/getLeastSupertype.h>
|
||||
#include <DataTypes/NumberTraits.h>
|
||||
#include <Interpreters/Context.h>
|
||||
#include <Interpreters/castColumn.h>
|
||||
#include <Columns/ColumnsNumber.h>
|
||||
#include <Core/Settings.h>
|
||||
#include <Functions/IFunction.h>
|
||||
#include <Functions/FunctionFactory.h>
|
||||
#include <base/map.h>
|
||||
@ -11,6 +13,11 @@
|
||||
namespace DB
|
||||
{
|
||||
|
||||
namespace Setting
|
||||
{
|
||||
extern const SettingsBool least_greatest_legacy_null_behavior;
|
||||
}
|
||||
|
||||
namespace ErrorCodes
|
||||
{
|
||||
extern const int NUMBER_OF_ARGUMENTS_DOESNT_MATCH;
|
||||
@ -29,14 +36,21 @@ class FunctionLeastGreatestGeneric : public IFunction
|
||||
{
|
||||
public:
|
||||
static constexpr auto name = kind == LeastGreatest::Least ? "least" : "greatest";
|
||||
static FunctionPtr create(ContextPtr) { return std::make_shared<FunctionLeastGreatestGeneric<kind>>(); }
|
||||
static FunctionPtr create(ContextPtr context) { return std::make_shared<FunctionLeastGreatestGeneric<kind>>(context); }
|
||||
|
||||
/// TODO Remove support for legacy NULL behavior (can be done end of 2026)
|
||||
|
||||
explicit FunctionLeastGreatestGeneric(ContextPtr context)
|
||||
: legacy_null_behavior(context->getSettingsRef()[Setting::least_greatest_legacy_null_behavior])
|
||||
{
|
||||
}
|
||||
|
||||
private:
|
||||
String getName() const override { return name; }
|
||||
size_t getNumberOfArguments() const override { return 0; }
|
||||
bool isVariadic() const override { return true; }
|
||||
bool useDefaultImplementationForConstants() const override { return true; }
|
||||
bool useDefaultImplementationForNulls() const override { return false; }
|
||||
bool useDefaultImplementationForNulls() const override { return legacy_null_behavior; }
|
||||
bool isSuitableForShortCircuitArgumentsExecution(const DataTypesWithConstInfo & /*arguments*/) const override { return false; }
|
||||
|
||||
DataTypePtr getReturnTypeImpl(const DataTypes & types) const override
|
||||
@ -55,15 +69,15 @@ private:
|
||||
Columns converted_columns;
|
||||
for (const auto & argument : arguments)
|
||||
{
|
||||
if (argument.type->onlyNull())
|
||||
if (!legacy_null_behavior && argument.type->onlyNull())
|
||||
continue; /// ignore NULL arguments
|
||||
auto converted_col = castColumn(argument, result_type)->convertToFullColumnIfConst();
|
||||
converted_columns.emplace_back(converted_col);
|
||||
converted_columns.push_back(converted_col);
|
||||
}
|
||||
|
||||
if (converted_columns.empty())
|
||||
if (!legacy_null_behavior && converted_columns.empty())
|
||||
return arguments[0].column;
|
||||
else if (converted_columns.size() == 1)
|
||||
else if (!legacy_null_behavior && converted_columns.size() == 1)
|
||||
return converted_columns[0];
|
||||
|
||||
auto result_column = result_type->createColumn();
|
||||
@ -93,6 +107,8 @@ private:
|
||||
|
||||
return result_column;
|
||||
}
|
||||
|
||||
bool legacy_null_behavior;
|
||||
};
|
||||
|
||||
template <LeastGreatest kind, typename SpecializedFunction>
|
||||
@ -100,18 +116,18 @@ class LeastGreatestOverloadResolver : public IFunctionOverloadResolver
|
||||
{
|
||||
public:
|
||||
static constexpr auto name = kind == LeastGreatest::Least ? "least" : "greatest";
|
||||
static FunctionOverloadResolverPtr create(ContextPtr context) { return std::make_unique<LeastGreatestOverloadResolver<kind, SpecializedFunction>>(context); }
|
||||
|
||||
static FunctionOverloadResolverPtr create(ContextPtr context)
|
||||
explicit LeastGreatestOverloadResolver(ContextPtr context_)
|
||||
: context(context_)
|
||||
, legacy_null_behavior(context->getSettingsRef()[Setting::least_greatest_legacy_null_behavior])
|
||||
{
|
||||
return std::make_unique<LeastGreatestOverloadResolver<kind, SpecializedFunction>>(context);
|
||||
}
|
||||
|
||||
explicit LeastGreatestOverloadResolver(ContextPtr context_) : context(context_) {}
|
||||
|
||||
String getName() const override { return name; }
|
||||
size_t getNumberOfArguments() const override { return 0; }
|
||||
bool isVariadic() const override { return true; }
|
||||
bool useDefaultImplementationForNulls() const override { return false; }
|
||||
bool useDefaultImplementationForNulls() const override { return legacy_null_behavior; }
|
||||
|
||||
FunctionBasePtr buildImpl(const ColumnsWithTypeAndName & arguments, const DataTypePtr & return_type) const override
|
||||
{
|
||||
@ -120,8 +136,13 @@ public:
|
||||
argument_types.push_back(argument.type);
|
||||
|
||||
/// More efficient specialization for two numeric arguments.
|
||||
if (arguments.size() == 2 && isNumber(arguments[0].type) && isNumber(arguments[1].type))
|
||||
if (arguments.size() == 2)
|
||||
{
|
||||
auto arg_0_type = legacy_null_behavior ? removeNullable(arguments[0].type) : arguments[0].type;
|
||||
auto arg_1_type = legacy_null_behavior ? removeNullable(arguments[1].type) : arguments[1].type;
|
||||
if (isNumber(arg_0_type) && isNumber(arg_1_type))
|
||||
return std::make_unique<FunctionToFunctionBaseAdaptor>(SpecializedFunction::create(context), argument_types, return_type);
|
||||
}
|
||||
|
||||
return std::make_unique<FunctionToFunctionBaseAdaptor>(
|
||||
FunctionLeastGreatestGeneric<kind>::create(context), argument_types, return_type);
|
||||
@ -132,14 +153,20 @@ public:
|
||||
if (types.empty())
|
||||
throw Exception(ErrorCodes::NUMBER_OF_ARGUMENTS_DOESNT_MATCH, "Function {} cannot be called without arguments", getName());
|
||||
|
||||
if (types.size() == 2 && isNumber(types[0]) && isNumber(types[1]))
|
||||
if (types.size() == 2)
|
||||
{
|
||||
auto arg_0_type = legacy_null_behavior ? removeNullable(types[0]) : types[0];
|
||||
auto arg_1_type = legacy_null_behavior ? removeNullable(types[1]) : types[1];
|
||||
if (isNumber(arg_0_type) && isNumber(arg_1_type))
|
||||
return SpecializedFunction::create(context)->getReturnTypeImpl(types);
|
||||
}
|
||||
|
||||
return getLeastSupertype(types);
|
||||
}
|
||||
|
||||
private:
|
||||
ContextPtr context;
|
||||
bool legacy_null_behavior;
|
||||
};
|
||||
|
||||
}
|
||||
|
@ -1,3 +1,4 @@
|
||||
Test with default NULL behavior
|
||||
Test with one const argument
|
||||
\N \N
|
||||
Test with two const arguments
|
||||
@ -19,3 +20,25 @@ a a
|
||||
Special cases
|
||||
2 1
|
||||
1 1
|
||||
Repeat above tests with legacy NULL behavior
|
||||
Test with one const argument
|
||||
\N \N
|
||||
Test with two const arguments
|
||||
\N \N
|
||||
\N \N
|
||||
\N \N
|
||||
\N \N
|
||||
\N \N
|
||||
\N \N
|
||||
Test with one non-const argument
|
||||
\N \N
|
||||
Test with two non-const arguments
|
||||
\N \N
|
||||
\N \N
|
||||
\N \N
|
||||
\N \N
|
||||
\N \N
|
||||
\N \N
|
||||
Special cases
|
||||
2 1
|
||||
\N \N
|
||||
|
@ -1,5 +1,39 @@
|
||||
-- Tests functions "greatest" and "least" with NULL arguments
|
||||
|
||||
SELECT 'Test with default NULL behavior';
|
||||
SET least_greatest_legacy_null_behavior = default;
|
||||
|
||||
SELECT 'Test with one const argument';
|
||||
SELECT greatest(NULL), least(NULL);
|
||||
|
||||
SELECT 'Test with two const arguments';
|
||||
SELECT greatest(1, NULL), least(1, NULL);
|
||||
SELECT greatest(NULL, 1), least(NULL, 1);
|
||||
SELECT greatest(NULL, 1.1), least(NULL, 1.1);
|
||||
SELECT greatest(1.1, NULL), least(1.1, NULL);
|
||||
SELECT greatest(NULL, 'a'), least(NULL, 'a');
|
||||
SELECT greatest('a', NULL), least('a', NULL);
|
||||
|
||||
SELECT 'Test with one non-const argument';
|
||||
SELECT greatest(materialize(NULL)), least(materialize(NULL));
|
||||
|
||||
SELECT 'Test with two non-const arguments';
|
||||
SELECT greatest(materialize(1), NULL), least(materialize(1), NULL);
|
||||
SELECT greatest(materialize(NULL), 1), least(materialize(NULL), 1);
|
||||
SELECT greatest(materialize(NULL), 1.1), least(materialize(NULL), 1.1);
|
||||
SELECT greatest(materialize(1.1), NULL), least(materialize(1.1), NULL);
|
||||
SELECT greatest(materialize(NULL), 'a'), least(materialize(NULL), 'a');
|
||||
SELECT greatest(materialize('a'), NULL), least(materialize('a'), NULL);
|
||||
|
||||
SELECT 'Special cases';
|
||||
SELECT greatest(toNullable(1), 2), least(toNullable(1), 2);
|
||||
SELECT greatest(toLowCardinality(1), NULL), least(toLowCardinality(1), NULL);
|
||||
|
||||
-- ----------------------------------------------------------------------------
|
||||
|
||||
SELECT 'Repeat above tests with legacy NULL behavior';
|
||||
SET least_greatest_legacy_null_behavior = true;
|
||||
|
||||
SELECT 'Test with one const argument';
|
||||
SELECT greatest(NULL), least(NULL);
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user