blob: 4c6955ca99a937177fa60475bf5cfcdebb74dc40 [file]
// Licensed to the Apache Software Foundation (ASF) under one
// or more contributor license agreements. See the NOTICE file
// distributed with this work for additional information
// regarding copyright ownership. The ASF licenses this file
// to you under the Apache License, Version 2.0 (the
// "License"); you may not use this file except in compliance
// with the License. You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing,
// software distributed under the License is distributed on an
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
// KIND, either express or implied. See the License for the
// specific language governing permissions and limitations
// under the License.
suite("test_pythonudaf_inline") {
// Test Python UDAF using Inline mode
def runtime_version = getPythonUdfRuntimeVersion()
try {
// Create test table
sql """ DROP TABLE IF EXISTS test_pythonudaf_inline_table """
sql """
CREATE TABLE IF NOT EXISTS test_pythonudaf_inline_table (
`id` INT NOT NULL COMMENT "ID",
`value` INT COMMENT "Value",
`amount` DOUBLE COMMENT "Amount",
`category` VARCHAR(10) NOT NULL COMMENT "Category"
)
DISTRIBUTED BY HASH(id) PROPERTIES("replication_num" = "1");
"""
// Insert test data
sql """ INSERT INTO test_pythonudaf_inline_table VALUES
(1, 10, 10.5, 'A'),
(2, 20, 20.5, 'A'),
(3, 30, 30.5, 'B'),
(4, 40, 40.5, 'B'),
(5, 50, 50.5, 'C');
"""
qt_select_data """ SELECT * FROM test_pythonudaf_inline_table ORDER BY id; """
// ========================================
// Test 1: Simple Sum UDAF (Inline)
// ========================================
sql """ DROP FUNCTION IF EXISTS udaf_sum_inline(INT); """
sql """
CREATE AGGREGATE FUNCTION udaf_sum_inline(INT)
RETURNS BIGINT
PROPERTIES (
"type" = "PYTHON_UDF",
"symbol" = "SumUDAF",
"runtime_version" = "${runtime_version}"
)
AS \$\$
class SumUDAF:
def __init__(self):
self.sum = 0
def accumulate(self, value):
if value is not None:
self.sum += value
def merge(self, other_state):
if other_state is not None:
self.sum += other_state
def finish(self):
return self.sum
@property
def aggregate_state(self):
return self.sum
\$\$;
"""
// Test basic aggregation
qt_test1 """ SELECT udaf_sum_inline(value) as total, sum(value) as native_sum FROM test_pythonudaf_inline_table; """
// Test with GROUP BY
qt_test2 """ SELECT category,
udaf_sum_inline(value) as sum_val,
sum(value) as native_sum
FROM test_pythonudaf_inline_table
GROUP BY category
ORDER BY category; """
// ========================================
// Test 2: Average UDAF (Inline)
// ========================================
sql """ DROP FUNCTION IF EXISTS udaf_avg_inline(DOUBLE); """
sql """
CREATE AGGREGATE FUNCTION udaf_avg_inline(DOUBLE)
RETURNS DOUBLE
PROPERTIES (
"type" = "PYTHON_UDF",
"symbol" = "AvgUDAF",
"runtime_version" = "${runtime_version}"
)
AS \$\$
class AvgUDAF:
def __init__(self):
self.count = 0
self.sum = 0.0
def accumulate(self, value):
if value is not None:
self.count += 1
self.sum += value
def merge(self, other_state):
if other_state is not None:
other_count, other_sum = other_state
self.count += other_count
self.sum += other_sum
def finish(self):
if self.count == 0:
return None
return self.sum / self.count
@property
def aggregate_state(self):
return (self.count, self.sum)
\$\$;
"""
qt_test3 """ SELECT udaf_avg_inline(amount) as avg_amount, avg(amount) as native_avg FROM test_pythonudaf_inline_table; """
qt_test4 """ SELECT category,
udaf_avg_inline(amount) as py_avg,
avg(amount) as native_avg
FROM test_pythonudaf_inline_table
GROUP BY category
ORDER BY category; """
// ========================================
// Test 3: Count UDAF (Inline)
// ========================================
sql """ DROP FUNCTION IF EXISTS udaf_count_inline(INT); """
sql """
CREATE AGGREGATE FUNCTION udaf_count_inline(INT)
RETURNS BIGINT
PROPERTIES (
"type" = "PYTHON_UDF",
"symbol" = "CountUDAF",
"runtime_version" = "${runtime_version}"
)
AS \$\$
class CountUDAF:
def __init__(self):
self.count = 0
def accumulate(self, value):
if value is not None:
self.count += 1
def merge(self, other_state):
if other_state is not None:
self.count += other_state
def finish(self):
return self.count
@property
def aggregate_state(self):
return self.count
\$\$;
"""
qt_test5 """ SELECT udaf_count_inline(value) as total_count, count(value) as native_count FROM test_pythonudaf_inline_table; """
qt_test6 """ SELECT category,
udaf_count_inline(value) as py_count,
count(value) as native_count
FROM test_pythonudaf_inline_table
GROUP BY category
ORDER BY category; """
// ========================================
// Test 4: Max UDAF (Inline)
// ========================================
sql """ DROP FUNCTION IF EXISTS udaf_max_inline(INT); """
sql """
CREATE AGGREGATE FUNCTION udaf_max_inline(INT)
RETURNS INT
PROPERTIES (
"type" = "PYTHON_UDF",
"symbol" = "MaxUDAF",
"runtime_version" = "${runtime_version}"
)
AS \$\$
class MaxUDAF:
def __init__(self):
self.max_value = None
def accumulate(self, value):
if value is not None:
if self.max_value is None or value > self.max_value:
self.max_value = value
def merge(self, other_state):
if other_state is not None:
if self.max_value is None or other_state > self.max_value:
self.max_value = other_state
def finish(self):
return self.max_value
@property
def aggregate_state(self):
return self.max_value
\$\$;
"""
qt_test7 """ SELECT udaf_max_inline(value) as max_value, max(value) as native_max FROM test_pythonudaf_inline_table; """
qt_test8 """ SELECT category,
udaf_max_inline(value) as py_max,
max(value) as native_max
FROM test_pythonudaf_inline_table
GROUP BY category
ORDER BY category; """
// ========================================
// Test 5: NULL handling
// ========================================
sql """ INSERT INTO test_pythonudaf_inline_table VALUES (6, NULL, 60.5, 'A'); """
sql """ INSERT INTO test_pythonudaf_inline_table VALUES (7, NULL, 70.5, 'B'); """
qt_test_null1 """ SELECT udaf_sum_inline(value) as total, sum(value) as native_sum FROM test_pythonudaf_inline_table; """
qt_test_null2 """ SELECT udaf_count_inline(value) as count, count(value) as native_count FROM test_pythonudaf_inline_table; """
qt_test_null3 """ SELECT category,
udaf_sum_inline(value) as sum_val,
sum(value) as native_sum
FROM test_pythonudaf_inline_table
GROUP BY category
ORDER BY category; """
// ========================================
// Test 6: Window Functions
// ========================================
qt_test_window1 """ SELECT id, category, value,
udaf_sum_inline(value) OVER(PARTITION BY category) as sum_by_cat,
sum(value) OVER(PARTITION BY category) as native_sum
FROM test_pythonudaf_inline_table
WHERE value IS NOT NULL
ORDER BY category, id; """
qt_test_window2 """ SELECT id, category, value,
udaf_sum_inline(value) OVER(PARTITION BY category ORDER BY id) as running_sum,
sum(value) OVER(PARTITION BY category ORDER BY id) as native_sum
FROM test_pythonudaf_inline_table
WHERE value IS NOT NULL
ORDER BY category, id; """
// ========================================
// Test 7: Multiple UDAFs in one query
// ========================================
qt_test_multiple """ SELECT category,
udaf_sum_inline(value) as sum_val,
sum(value) as native_sum,
udaf_count_inline(value) as count_val,
count(value) as native_count,
udaf_max_inline(value) as max_val,
max(value) as native_max,
udaf_avg_inline(amount) as avg_amount,
avg(amount) as native_avg
FROM test_pythonudaf_inline_table
GROUP BY category
ORDER BY category; """
// ========================================
// Test 8: Global Function
// ========================================
sql """ DROP GLOBAL FUNCTION IF EXISTS udaf_sum_global(INT); """
sql """
CREATE GLOBAL AGGREGATE FUNCTION udaf_sum_global(INT)
RETURNS BIGINT
PROPERTIES (
"type" = "PYTHON_UDF",
"symbol" = "SumUDAF",
"runtime_version" = "${runtime_version}"
)
AS \$\$
class SumUDAF:
def __init__(self):
self.sum = 0
def accumulate(self, value):
if value is not None:
self.sum += value
def merge(self, other_state):
if other_state is not None:
self.sum += other_state
def finish(self):
return self.sum
@property
def aggregate_state(self):
return self.sum
\$\$;
"""
qt_test_global1 """ SELECT udaf_sum_global(value) as total, sum(value) as native_sum FROM test_pythonudaf_inline_table; """
qt_test_global2 """ SELECT category,
udaf_sum_global(value) as sum_val,
sum(value) as native_sum
FROM test_pythonudaf_inline_table
GROUP BY category
ORDER BY category; """
// Empty input with high pipeline parallelism should still succeed.
qt_test_empty_parallel """ SELECT /*+SET_VAR(parallel_pipeline_task_num=8)*/
udaf_sum_inline(value) as total
FROM test_pythonudaf_inline_table
WHERE id < 0; """
qt_test_global_empty_parallel """ SELECT /*+SET_VAR(parallel_pipeline_task_num=8)*/
udaf_sum_global(value) as total
FROM test_pythonudaf_inline_table
WHERE id < 0; """
// ========================================
// Test 9: LARGEINT Sum UDAF (Inline)
// ========================================
sql """ DROP TABLE IF EXISTS test_pythonudaf_convert_type_table """
sql """
CREATE TABLE IF NOT EXISTS test_pythonudaf_convert_type_table (
`id` INT NOT NULL,
`val` LARGEINT,
`category` VARCHAR(10) NOT NULL,
`ip_v4` IPV4,
`ip_v6` IPV6
)
DISTRIBUTED BY HASH(id) PROPERTIES("replication_num" = "1");
"""
sql """ INSERT INTO test_pythonudaf_convert_type_table VALUES
(1, 100, 'A', '192.168.1.1', '2001:db8::1'),
(2, 200, 'A', '10.0.0.1', '::1'),
(3, 300, 'B', '8.8.8.8', '2001:4860:4860::8888'),
(4, 400, 'B', '172.16.0.1', 'fe80::1'),
(5, NULL, 'A', NULL, NULL);
"""
sql """ DROP FUNCTION IF EXISTS udaf_sum_largeint_inline(LARGEINT); """
sql """
CREATE AGGREGATE FUNCTION udaf_sum_largeint_inline(LARGEINT)
RETURNS LARGEINT
PROPERTIES (
"type" = "PYTHON_UDF",
"symbol" = "SumLargeIntUDAF",
"runtime_version" = "${runtime_version}"
)
AS \$\$
class SumLargeIntUDAF:
def __init__(self):
self.sum = 0
def accumulate(self, value):
if value is not None:
self.sum += value
def merge(self, other_state):
if other_state is not None:
self.sum += other_state
def finish(self):
return self.sum
@property
def aggregate_state(self):
return self.sum
\$\$;
"""
qt_test_largeint1 """ SELECT udaf_sum_largeint_inline(val) as total FROM test_pythonudaf_convert_type_table; """
qt_test_largeint2 """ SELECT category,
udaf_sum_largeint_inline(val) as sum_val
FROM test_pythonudaf_convert_type_table
GROUP BY category
ORDER BY category; """
// ========================================
// Test 10: IPv4 UDAF input type conversion
// ========================================
sql """ DROP FUNCTION IF EXISTS udaf_count_private_ipv4_inline(IPV4); """
sql """
CREATE AGGREGATE FUNCTION udaf_count_private_ipv4_inline(IPV4)
RETURNS BIGINT
PROPERTIES (
"type" = "PYTHON_UDF",
"symbol" = "CountPrivateIPv4UDAF",
"runtime_version" = "${runtime_version}"
)
AS \$\$
class CountPrivateIPv4UDAF:
def __init__(self):
self.count = 0
def accumulate(self, value):
if value is not None and value.is_private:
self.count += 1
def merge(self, other_state):
if other_state is not None:
self.count += other_state
def finish(self):
return self.count
@property
def aggregate_state(self):
return self.count
\$\$;
"""
qt_test_ipv4_udaf1 """ SELECT udaf_count_private_ipv4_inline(ip_v4) as private_ipv4_count
FROM test_pythonudaf_convert_type_table; """
qt_test_ipv4_udaf2 """ SELECT category,
udaf_count_private_ipv4_inline(ip_v4) as private_ipv4_count
FROM test_pythonudaf_convert_type_table
GROUP BY category
ORDER BY category; """
// ========================================
// Test 11: IPv6 UDAF input type conversion
// ========================================
sql """ DROP FUNCTION IF EXISTS udaf_count_loopback_ipv6_inline(IPV6); """
sql """
CREATE AGGREGATE FUNCTION udaf_count_loopback_ipv6_inline(IPV6)
RETURNS BIGINT
PROPERTIES (
"type" = "PYTHON_UDF",
"symbol" = "CountLoopbackIPv6UDAF",
"runtime_version" = "${runtime_version}"
)
AS \$\$
class CountLoopbackIPv6UDAF:
def __init__(self):
self.count = 0
def accumulate(self, value):
if value is not None and value.is_loopback:
self.count += 1
def merge(self, other_state):
if other_state is not None:
self.count += other_state
def finish(self):
return self.count
@property
def aggregate_state(self):
return self.count
\$\$;
"""
qt_test_ipv6_udaf1 """ SELECT udaf_count_loopback_ipv6_inline(ip_v6) as loopback_ipv6_count
FROM test_pythonudaf_convert_type_table; """
qt_test_ipv6_udaf2 """ SELECT category,
udaf_count_loopback_ipv6_inline(ip_v6) as loopback_ipv6_count
FROM test_pythonudaf_convert_type_table
GROUP BY category
ORDER BY category; """
} finally {
try_sql("DROP GLOBAL FUNCTION IF EXISTS udaf_sum_global(INT);")
try_sql("DROP FUNCTION IF EXISTS udaf_sum_inline(INT);")
try_sql("DROP FUNCTION IF EXISTS udaf_avg_inline(DOUBLE);")
try_sql("DROP FUNCTION IF EXISTS udaf_count_inline(INT);")
try_sql("DROP FUNCTION IF EXISTS udaf_max_inline(INT);")
try_sql("DROP FUNCTION IF EXISTS udaf_sum_largeint_inline(LARGEINT);")
try_sql("DROP FUNCTION IF EXISTS udaf_count_private_ipv4_inline(IPV4);")
try_sql("DROP FUNCTION IF EXISTS udaf_count_loopback_ipv6_inline(IPV6);")
try_sql("DROP TABLE IF EXISTS test_pythonudaf_inline_table")
try_sql("DROP TABLE IF EXISTS test_pythonudaf_convert_type_table")
}
}