Skip to content

Commit 4a54d21

Browse files
Merge pull request ClickHouse#80073 from nibblerenush/log_udf_name
Add executable UDF names to the `used_functions` column in the `system.query_log`.
2 parents e900946 + 34650e3 commit 4a54d21

File tree

7 files changed

+183
-0
lines changed

7 files changed

+183
-0
lines changed

src/Functions/UserDefined/UserDefinedExecutableFunctionFactory.cpp

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,11 @@
22

33
#include <filesystem>
44

5+
#include <Common/CurrentThread.h>
56
#include <Common/filesystemHelpers.h>
67
#include <Common/FieldVisitorToString.h>
78
#include <Common/quoteString.h>
9+
#include <Core/Settings.h>
810
#include <DataTypes/FieldToDataType.h>
911

1012
#include <Processors/Sources/ShellCommandSource.h>
@@ -28,6 +30,11 @@ namespace ErrorCodes
2830
extern const int BAD_ARGUMENTS;
2931
}
3032

33+
namespace Setting
34+
{
35+
extern const SettingsBool log_queries;
36+
}
37+
3138
namespace
3239
{
3340

@@ -245,6 +252,14 @@ FunctionOverloadResolverPtr UserDefinedExecutableFunctionFactory::get(const Stri
245252
const auto & loader = context->getExternalUserDefinedExecutableFunctionsLoader();
246253
auto executable_function = std::static_pointer_cast<const UserDefinedExecutableFunction>(loader.load(function_name));
247254
auto function = std::make_shared<UserDefinedFunction>(std::move(executable_function), std::move(context), std::move(parameters));
255+
256+
if (CurrentThread::isInitialized())
257+
{
258+
auto query_context = CurrentThread::get().getQueryContext();
259+
if (query_context && query_context->getSettingsRef()[Setting::log_queries])
260+
query_context->addQueryFactoriesInfo(Context::QueryLogFactories::Function, function_name);
261+
}
262+
248263
return std::make_unique<FunctionToOverloadResolverAdaptor>(std::move(function));
249264
}
250265

@@ -257,6 +272,14 @@ FunctionOverloadResolverPtr UserDefinedExecutableFunctionFactory::tryGet(const S
257272
{
258273
auto executable_function = std::static_pointer_cast<const UserDefinedExecutableFunction>(load_result.object);
259274
auto function = std::make_shared<UserDefinedFunction>(std::move(executable_function), std::move(context), std::move(parameters));
275+
276+
if (CurrentThread::isInitialized())
277+
{
278+
auto query_context = CurrentThread::get().getQueryContext();
279+
if (query_context && query_context->getSettingsRef()[Setting::log_queries])
280+
query_context->addQueryFactoriesInfo(Context::QueryLogFactories::Function, function_name);
281+
}
282+
260283
return std::make_unique<FunctionToOverloadResolverAdaptor>(std::move(function));
261284
}
262285

tests/integration/test_executable_udf_names_in_used_functions_column/__init__.py

Whitespace-only changes.
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
<clickhouse>
2+
</clickhouse>
Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
<functions>
2+
<function>
3+
<type>executable</type>
4+
<name>test_function_bash</name>
5+
<return_type>String</return_type>
6+
<argument>
7+
<type>String</type>
8+
</argument>
9+
<format>TabSeparated</format>
10+
<command>input.sh</command>
11+
</function>
12+
13+
<function>
14+
<type>executable_pool</type>
15+
<name>test_function_pool_bash</name>
16+
<return_type>String</return_type>
17+
<argument>
18+
<type>String</type>
19+
</argument>
20+
<format>TabSeparated</format>
21+
<command>input.sh</command>
22+
</function>
23+
24+
<function>
25+
<type>executable</type>
26+
<name>test_function_python</name>
27+
<return_type>String</return_type>
28+
<argument>
29+
<type>String</type>
30+
</argument>
31+
<format>TabSeparated</format>
32+
<command>input.py</command>
33+
</function>
34+
35+
<function>
36+
<type>executable_pool</type>
37+
<name>test_function_pool_python</name>
38+
<return_type>String</return_type>
39+
<argument>
40+
<type>String</type>
41+
</argument>
42+
<format>TabSeparated</format>
43+
<command>input.py</command>
44+
</function>
45+
</functions>
Lines changed: 100 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,100 @@
1+
import os
2+
import sys
3+
import time
4+
import uuid
5+
6+
import pytest
7+
8+
sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
9+
SCRIPT_DIR = os.path.dirname(os.path.realpath(__file__))
10+
11+
from helpers.cluster import ClickHouseCluster
12+
13+
cluster = ClickHouseCluster(__file__)
14+
node = cluster.add_instance("node", stay_alive=True, main_configs=[])
15+
16+
17+
def skip_test_msan(instance):
18+
if instance.is_built_with_memory_sanitizer():
19+
pytest.skip("Memory Sanitizer cannot work with vfork")
20+
21+
22+
def copy_file_to_container(local_path, dist_path, container_id):
23+
os.system(
24+
"docker cp {local} {cont_id}:{dist}".format(
25+
local=local_path, cont_id=container_id, dist=dist_path
26+
)
27+
)
28+
29+
30+
config = """<clickhouse>
31+
<user_defined_executable_functions_config>/etc/clickhouse-server/functions/test_function_config.xml</user_defined_executable_functions_config>
32+
</clickhouse>"""
33+
34+
35+
@pytest.fixture(scope="module")
36+
def started_cluster():
37+
try:
38+
cluster.start()
39+
40+
node.replace_config(
41+
"/etc/clickhouse-server/config.d/executable_user_defined_functions_config.xml",
42+
config,
43+
)
44+
45+
copy_file_to_container(
46+
os.path.join(SCRIPT_DIR, "functions/."),
47+
"/etc/clickhouse-server/functions",
48+
node.docker_id,
49+
)
50+
copy_file_to_container(
51+
os.path.join(SCRIPT_DIR, "user_scripts/."),
52+
"/var/lib/clickhouse/user_scripts",
53+
node.docker_id,
54+
)
55+
56+
node.restart_clickhouse()
57+
58+
yield cluster
59+
60+
finally:
61+
cluster.shutdown()
62+
63+
64+
def check_executable_udf_functions(functions):
65+
if not functions:
66+
assert False
67+
execute_udf = "SELECT {}" if len(functions) == 1 else "SELECT concat({})"
68+
execute_udf = execute_udf.format(','.join(map(lambda function : "{}(1)".format(function), functions)))
69+
query_id = uuid.uuid4().hex
70+
node.query(execute_udf, query_id=query_id)
71+
node.query("SYSTEM FLUSH LOGS")
72+
73+
used_functions = set(map(repr, functions))
74+
if len(functions) > 1:
75+
used_functions.add("\'concat\'")
76+
77+
query_result = node.query("SELECT used_functions FROM system.query_log "
78+
"WHERE type = \'QueryFinish\' AND query_id = \'{}\'".format(query_id))
79+
query_result = set(query_result[1:len(query_result) - 2].split(','))
80+
assert query_result == used_functions
81+
82+
83+
def test_executable_function_single(started_cluster):
84+
skip_test_msan(node)
85+
check_executable_udf_functions(["test_function_bash"])
86+
87+
88+
def test_executable_function_multiple(started_cluster):
89+
skip_test_msan(node)
90+
check_executable_udf_functions(["test_function_bash", "test_function_python"])
91+
92+
93+
def test_executable_function_multiple_pool(started_cluster):
94+
skip_test_msan(node)
95+
check_executable_udf_functions(["test_function_bash", "test_function_python", "test_function_pool_bash", "test_function_pool_python"])
96+
97+
98+
def test_executable_function_duplicate(started_cluster):
99+
skip_test_msan(node)
100+
check_executable_udf_functions(["test_function_bash", "test_function_bash"])
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
#!/usr/bin/python3
2+
3+
import sys
4+
5+
if __name__ == "__main__":
6+
for line in sys.stdin:
7+
print(line, end="")
8+
sys.stdout.flush()
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
#!/bin/bash
2+
3+
while read read_data;
4+
do printf "$read_data\n";
5+
done

0 commit comments

Comments
 (0)