Skip to content

Commit b871ded

Browse files
authored
Merge pull request ClickHouse#90567 from ClickHouse/backport/25.8/88441
Backport ClickHouse#88441 to 25.8: Backporting function firstNonDefault
2 parents 5f4e835 + ea6d6f1 commit b871ded

File tree

3 files changed

+328
-0
lines changed

3 files changed

+328
-0
lines changed

src/Functions/firstNonDefault.cpp

Lines changed: 153 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,153 @@
1+
#include <Columns/IColumn.h>
2+
3+
#include <DataTypes/getLeastSupertype.h>
4+
5+
#include <Functions/FunctionFactory.h>
6+
#include <Functions/IFunction.h>
7+
8+
#include <Interpreters/castColumn.h>
9+
#include <Interpreters/Context_fwd.h>
10+
11+
namespace DB
12+
{
13+
14+
namespace ErrorCodes
15+
{
16+
extern const int LOGICAL_ERROR;
17+
extern const int NUMBER_OF_ARGUMENTS_DOESNT_MATCH;
18+
}
19+
20+
namespace
21+
{
22+
23+
/// Implements the function which takes a set of arguments and
24+
/// returns the value of the leftmost non-falsey argument.
25+
/// If all arguments are falsey, returns the default value for the result type.
26+
/// Result type is the supertype of all arguments.
27+
class FunctionFirstNonDefault : public IFunction
28+
{
29+
public:
30+
static constexpr auto name = "firstNonDefault";
31+
32+
static FunctionPtr create(ContextPtr)
33+
{
34+
return std::make_shared<FunctionFirstNonDefault>();
35+
}
36+
37+
FunctionFirstNonDefault() = default;
38+
39+
String getName() const override { return name; }
40+
bool useDefaultImplementationForConstants() const override { return true; }
41+
bool useDefaultImplementationForLowCardinalityColumns() const override { return false; }
42+
bool useDefaultImplementationForNulls() const override { return false; }
43+
bool isVariadic() const override { return true; }
44+
bool isSuitableForShortCircuitArgumentsExecution(const DataTypesWithConstInfo & /*arguments*/) const override { return false; }
45+
size_t getNumberOfArguments() const override { return 0; }
46+
47+
ColumnNumbers getArgumentsThatDontImplyNullableReturnType(size_t number_of_arguments) const override
48+
{
49+
ColumnNumbers args;
50+
for (size_t i = 0; i + 1 < number_of_arguments; ++i)
51+
args.push_back(i);
52+
return args;
53+
}
54+
55+
DataTypePtr getReturnTypeImpl(const DataTypes & arguments) const override
56+
{
57+
if (arguments.empty())
58+
throw Exception(ErrorCodes::NUMBER_OF_ARGUMENTS_DOESNT_MATCH,
59+
"Function {} requires at least one argument", getName());
60+
size_t max_args = 1024;
61+
if (arguments.size() > max_args)
62+
throw Exception(ErrorCodes::NUMBER_OF_ARGUMENTS_DOESNT_MATCH,
63+
"Function {} requires at most {} arguments, got {}", getName(), max_args, arguments.size());
64+
65+
if (arguments.size() == 1)
66+
return arguments[0];
67+
68+
return getLeastSupertype(arguments);
69+
}
70+
71+
ColumnPtr executeImpl(const ColumnsWithTypeAndName & arguments, const DataTypePtr & result_type, size_t input_rows_count) const override
72+
{
73+
if (arguments.size() == 1)
74+
return arguments[0].column;
75+
76+
size_t num_columns = arguments.size();
77+
78+
auto result_col = result_type->createColumn();
79+
result_col->reserve(input_rows_count);
80+
81+
/// Cast all arguments to the result type
82+
/// Use this columns to insert values into the result column
83+
std::vector<ColumnPtr> casted_columns;
84+
casted_columns.reserve(num_columns);
85+
for (const auto & arg : arguments)
86+
{
87+
auto casted_column = castColumn(arg, result_type);
88+
casted_column = casted_column->convertToFullColumnIfConst();
89+
casted_column = casted_column->convertToFullColumnIfSparse();
90+
91+
if (casted_column->getDataType() != result_col->getDataType())
92+
{
93+
throw Exception(ErrorCodes::LOGICAL_ERROR,
94+
"All arguments must cast to the same type, got {} and {} for result type {}",
95+
casted_column->dumpStructure(), result_col->dumpStructure(), result_type->getName());
96+
}
97+
98+
casted_columns.push_back(std::move(casted_column));
99+
}
100+
101+
for (size_t row = 0; row < input_rows_count; ++row)
102+
{
103+
bool found = false;
104+
105+
/// Check each argument for truthiness
106+
for (size_t arg_idx = 0; !found && arg_idx < num_columns; ++arg_idx)
107+
{
108+
/// A value is considered "falsey" if it's NULL or the default value for its type
109+
/// For example:
110+
/// - for numeric types, the default is 0
111+
/// - for strings, the default is ''
112+
/// - for arrays, the default is []
113+
if (!arguments[arg_idx].column->isDefaultAt(row))
114+
{
115+
/// Found a truthy value, insert it into the result
116+
result_col->insertFrom(*casted_columns[arg_idx], row);
117+
found = true;
118+
}
119+
}
120+
121+
if (!found)
122+
result_col->insertDefault();
123+
}
124+
return result_col;
125+
}
126+
};
127+
128+
}
129+
130+
REGISTER_FUNCTION(FirstNonDefault)
131+
{
132+
FunctionDocumentation doc;
133+
doc.description = "Returns the first non-default value from a set of arguments";
134+
doc.arguments = {
135+
{"arg1", "The first argument to check"},
136+
{"arg2", "The second argument to check"},
137+
{"...", "Additional arguments to check"},
138+
};
139+
140+
doc.returned_value = FunctionDocumentation::ReturnedValue{"Result type is the supertype of all arguments", {}};
141+
doc.examples = {
142+
{"integers", "SELECT firstNonDefault(0, 1, 2)", "1"},
143+
{"strings", "SELECT firstNonDefault('', 'hello', 'world')", "'hello'"},
144+
{"nulls", "SELECT firstNonDefault(NULL, 0 :: UInt8, 1 :: UInt8)", "1"},
145+
{"nullable zero", "SELECT firstNonDefault(NULL, 0 :: Nullable(UInt8), 1 :: Nullable(UInt8))", "0"},
146+
};
147+
doc.category = {FunctionDocumentation::Category::Null};
148+
149+
doc.introduced_in = {25, 9};
150+
factory.registerFunction<FunctionFirstNonDefault>(doc, FunctionFactory::Case::Insensitive);
151+
}
152+
153+
}
Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
43
2+
0
3+
0
4+
\N
5+
true
6+
[1,2,3]
7+
foo Nullable(String)
8+
42 UInt32
9+
42 Int32
10+
42 UInt128
11+
42 Int128
12+
42 Int16
13+
42 Int64
14+
42.5 Float64
15+
42 Float64
16+
0 Nullable(Int32)
17+
42 Nullable(Int32)
18+
0 String
19+
0 String
20+
[0] Array(Int32)
21+
[''] Array(String)
22+
42 Nullable(UInt8)
23+
0 Nullable(String)
24+
\N Nullable(Nothing)
25+
0 UInt8
26+
String
27+
[] Array(UInt8)
28+
0
29+
1
30+
2
31+
0 0 0 Nullable(Int32)
32+
0 0 0 Nullable(Int32)
33+
0 0 0 Nullable(Int32)
34+
0 2 0 Nullable(Int32)
35+
0 \N 0 Nullable(Int32)
36+
1 0 1 Nullable(Int32)
37+
\N 0 0 Nullable(Int32)
38+
\N \N \N Nullable(Int32)
39+
Nullable(String)
40+
Nullable(String)
41+
Nullable(String)
42+
Nullable(String)
43+
Nullable(String)
44+
hello hello Nullable(String)
45+
\N default Nullable(String)
46+
\N default Nullable(String)
47+
[] [99,100] Array(Int32)
48+
[] [99,100] Array(Int32)
49+
[] [99,100] Array(Int32)
50+
[] [99,100] Array(Int32)
51+
[] [99,100] Array(Int32)
52+
[] [99,100] Array(Int32)
53+
[] [99,100] Array(Int32)
54+
[1,2,3] [1,2,3] Array(Int32)
55+
0 0 0 Nullable(Int64)
56+
0 0 0 Nullable(Int64)
57+
0 0 0 Nullable(Int64)
58+
0 2 2 Nullable(Int64)
59+
0 \N \N Nullable(Int64)
60+
1 0 1 Nullable(Int64)
61+
\N 0 \N Nullable(Int64)
62+
\N \N \N Nullable(Int64)
63+
0 0 42 0 0
64+
0 0 42 0 0
65+
0 0 42 0 0
66+
0 2 42 0 0
67+
0 \N 42 0 0
68+
1 0 42 1 1
69+
\N 0 42 0 0
70+
\N \N 42 \N \N
Lines changed: 105 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,105 @@
1+
SELECT firstNonDefault(NULL, 0, 43, 256) AS result;
2+
SELECT firstNonDefault(NULL :: Nullable(UInt8), 0 :: Nullable(UInt8), 42 :: UInt8) AS result;
3+
SELECT firstNonDefault('', '0', 'hello') AS result;
4+
SELECT firstNonDefault(NULL::Nullable(UInt8), 0::UInt8) AS result;
5+
SELECT firstNonDefault(false, true) AS result;
6+
7+
SELECT firstNonDefault([] :: Array(UInt8), [1, 2, 3] :: Array(UInt8)) AS result;
8+
SELECT firstNonDefault(NULL::Nullable(String), ''::String, 'foo') as result, toTypeName(result);
9+
10+
SELECT firstNonDefault(0::UInt8, 0::UInt16, 42::UInt32) AS result, toTypeName(result);
11+
SELECT firstNonDefault(0::Int8, 0::Int16, 42::Int32) AS result, toTypeName(result);
12+
SELECT firstNonDefault(0::UInt32, 0::UInt64, 42::UInt128) AS result, toTypeName(result);
13+
SELECT firstNonDefault(0::Int128, 0::Int128, 42::Int128) AS result, toTypeName(result);
14+
SELECT firstNonDefault(0::UInt8, 0::Int8, 42::Int16) AS result, toTypeName(result);
15+
SELECT firstNonDefault(0::Int64, 0::Int64, 42::Int64) AS result, toTypeName(result);
16+
SELECT firstNonDefault(0.0::Float32, 0.0::Float64, 42.5::Float64) AS result, toTypeName(result);
17+
SELECT firstNonDefault(0::Float64, 0.0::Float64, 42.0::Float64) AS result, toTypeName(result);
18+
SELECT firstNonDefault(NULL::Nullable(Int32), 0::Nullable(Int32), 42::Nullable(Int32)) AS result, toTypeName(result);
19+
SELECT firstNonDefault(NULL, 0::Int32, 42::Nullable(Int32)) AS result, toTypeName(result);
20+
SELECT firstNonDefault(''::String, '0'::String, 'hello'::String) AS result, toTypeName(result);
21+
SELECT firstNonDefault(''::FixedString(5), '0'::String, 'hello'::String) AS result, toTypeName(result);
22+
SELECT firstNonDefault([]::Array(Int32), [0]::Array(Int32), [1, 2, 3]::Array(Int32)) AS result, toTypeName(result);
23+
SELECT firstNonDefault([]::Array(String), ['']::Array(String), ['hello']::Array(String)) AS result, toTypeName(result);
24+
SELECT firstNonDefault(NULL::Nullable(UInt8), 0::UInt8, 42::UInt8, 100::UInt8) AS result, toTypeName(result);
25+
SELECT firstNonDefault(NULL::Nullable(String), ''::String, '0'::String, 'hello'::String) AS result, toTypeName(result);
26+
27+
SELECT firstNonDefault(NULL) AS result, toTypeName(result);
28+
SELECT firstNonDefault(0) AS result, toTypeName(result);
29+
SELECT firstNonDefault(''::String) AS result, toTypeName(result);
30+
SELECT firstNonDefault([]::Array(UInt8)) AS result, toTypeName(result);
31+
32+
SELECT firstNonDefault(); -- { serverError NUMBER_OF_ARGUMENTS_DOESNT_MATCH }
33+
34+
SELECT firstNonDefault(0, 'hello'); -- { serverError NO_COMMON_TYPE }
35+
SELECT firstNonDefault([]::Array(UInt8), 42); -- { serverError NO_COMMON_TYPE }
36+
SELECT firstNonDefault([]::Array(UInt8), 'hello'); -- { serverError NO_COMMON_TYPE }
37+
SELECT firstNonDefault(0::UInt64, 1::Int64); -- { serverError NO_COMMON_TYPE }
38+
SELECT firstNonDefault(NULL::Nullable(Array(UInt8)), []::Array(UInt8)); -- { serverError ILLEGAL_TYPE_OF_ARGUMENT }
39+
40+
SELECT firstNonDefault(
41+
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
42+
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
43+
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
44+
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
45+
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
46+
number
47+
) FROM numbers(3);
48+
49+
DROP TABLE IF EXISTS test_first_truthy;
50+
51+
CREATE TABLE test_first_truthy
52+
(
53+
a Nullable(Int32),
54+
b Nullable(Int32),
55+
c Nullable(String),
56+
d Array(Int32)
57+
) ENGINE = Memory;
58+
59+
INSERT INTO test_first_truthy VALUES
60+
(NULL, 0, NULL, []),
61+
(0, NULL, '', []),
62+
(NULL, NULL, NULL, []),
63+
(0, 0, '', []),
64+
(1, 0, '', []),
65+
(0, 2, '', []),
66+
(0, 0, 'hello', []),
67+
(0, 0, '', [1, 2, 3]);
68+
69+
SELECT
70+
a, b,
71+
firstNonDefault(a, b) AS result,
72+
toTypeName(firstNonDefault(a, b)) AS type
73+
FROM test_first_truthy
74+
ORDER BY ALL;
75+
76+
SELECT
77+
c,
78+
firstNonDefault(c, 'default'::String) AS result,
79+
toTypeName(firstNonDefault(c, 'default'::String)) AS type
80+
FROM test_first_truthy
81+
ORDER BY ALL;
82+
83+
SELECT
84+
d,
85+
firstNonDefault(d, [99, 100]::Array(Int32)) AS result,
86+
toTypeName(firstNonDefault(d, [99, 100]::Array(Int32))) AS type
87+
FROM test_first_truthy
88+
ORDER BY length(result);
89+
90+
SELECT
91+
a, b,
92+
firstNonDefault(a + b, a * b, a - b) AS result,
93+
toTypeName(firstNonDefault(a + b, a * b, a - b)) AS type
94+
FROM test_first_truthy
95+
ORDER BY ALL;
96+
97+
SELECT
98+
a, b,
99+
firstNonDefault(42, a, b) AS result1,
100+
firstNonDefault(0, a, b) AS result2,
101+
firstNonDefault(NULL, a, b) AS result3
102+
FROM test_first_truthy
103+
ORDER BY ALL;
104+
105+
DROP TABLE test_first_truthy;

0 commit comments

Comments
 (0)