blob: 970b7517574bbb7abe7afb19391622232e404796 [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_pythonudwf_comprehensive") {
// Comprehensive test suite for Python User-Defined Window Functions (UDWF)
// Tests cover: PARTITION BY, ORDER BY, frame specifications, edge cases, and complex scenarios
def runtime_version = getPythonUdfRuntimeVersion()
try {
// ========================================
// Setup: Create test tables with diverse data
// ========================================
// Table 1: Sales data for basic window function tests
sql """ DROP TABLE IF EXISTS udwf_sales; """
sql """
CREATE TABLE udwf_sales (
order_id INT,
order_date DATE,
region VARCHAR(50),
product VARCHAR(50),
category VARCHAR(50),
sales_amount DECIMAL(10,2),
quantity INT,
salesperson VARCHAR(50)
) ENGINE=OLAP
DUPLICATE KEY(order_id)
DISTRIBUTED BY HASH(order_id) BUCKETS 3
PROPERTIES("replication_num" = "1");
"""
sql """
INSERT INTO udwf_sales VALUES
(1, '2024-01-01', 'North', 'Laptop', 'Electronics', 1200.50, 2, 'Alice'),
(2, '2024-01-02', 'North', 'Mouse', 'Electronics', 25.99, 10, 'Alice'),
(3, '2024-01-03', 'North', 'Desk', 'Furniture', 350.00, 1, 'Bob'),
(4, '2024-01-04', 'South', 'Laptop', 'Electronics', 1150.00, 1, 'Charlie'),
(5, '2024-01-05', 'South', 'Chair', 'Furniture', 200.00, 4, 'Charlie'),
(6, '2024-01-06', 'South', 'Monitor', 'Electronics', 300.00, 3, 'David'),
(7, '2024-01-07', 'East', 'Laptop', 'Electronics', 1300.00, 2, 'Eve'),
(8, '2024-01-08', 'East', 'Keyboard', 'Electronics', 80.00, 5, 'Eve'),
(9, '2024-01-09', 'East', 'Bookshelf', 'Furniture', 180.00, 2, 'Frank'),
(10, '2024-01-10', 'West', 'Laptop', 'Electronics', 1250.00, 3, 'Grace'),
(11, '2024-01-11', 'West', 'Mouse', 'Electronics', 22.50, 12, 'Grace'),
(12, '2024-01-12', 'West', 'Table', 'Furniture', 450.00, 1, 'Henry'),
(13, '2024-01-13', 'North', 'Monitor', 'Electronics', 310.00, 2, 'Alice'),
(14, '2024-01-14', 'South', 'Keyboard', 'Electronics', 78.00, 3, 'Charlie'),
(15, '2024-01-15', 'East', 'Mouse', 'Electronics', 24.00, 8, 'Eve'),
(16, '2024-01-16', 'West', 'Chair', 'Furniture', 195.00, 5, 'Grace'),
(17, '2024-01-17', 'North', 'Desk', 'Furniture', 380.00, 1, 'Bob'),
(18, '2024-01-18', 'South', 'Monitor', 'Electronics', 295.00, 2, 'David'),
(19, '2024-01-19', 'East', 'Laptop', 'Electronics', 1280.00, 1, 'Frank'),
(20, '2024-01-20', 'West', 'Keyboard', 'Electronics', 85.00, 6, 'Henry');
"""
// Table 2: Stock prices for time-series analysis
sql """ DROP TABLE IF EXISTS udwf_stock_prices; """
sql """
CREATE TABLE udwf_stock_prices (
trade_id INT,
trade_time DATETIME,
symbol VARCHAR(20),
price DOUBLE,
volume INT
) ENGINE=OLAP
DUPLICATE KEY(trade_id)
DISTRIBUTED BY HASH(trade_id) BUCKETS 2
PROPERTIES("replication_num" = "1");
"""
sql """
INSERT INTO udwf_stock_prices VALUES
(1, '2024-01-01 09:30:00', 'AAPL', 150.25, 1000),
(2, '2024-01-01 09:35:00', 'AAPL', 151.50, 1200),
(3, '2024-01-01 09:40:00', 'AAPL', 150.75, 800),
(4, '2024-01-01 09:45:00', 'AAPL', 152.00, 1500),
(5, '2024-01-01 09:50:00', 'AAPL', 151.25, 900),
(6, '2024-01-01 09:30:00', 'GOOGL', 2800.00, 500),
(7, '2024-01-01 09:35:00', 'GOOGL', 2815.50, 600),
(8, '2024-01-01 09:40:00', 'GOOGL', 2810.00, 400),
(9, '2024-01-01 09:45:00', 'GOOGL', 2825.00, 700),
(10, '2024-01-01 09:50:00', 'GOOGL', 2820.50, 550),
(11, '2024-01-01 09:30:00', 'MSFT', 380.00, 2000),
(12, '2024-01-01 09:35:00', 'MSFT', 382.50, 2200),
(13, '2024-01-01 09:40:00', 'MSFT', 381.00, 1800),
(14, '2024-01-01 09:45:00', 'MSFT', 383.75, 2500),
(15, '2024-01-01 09:50:00', 'MSFT', 382.25, 1900);
"""
// Table 3: Student scores for ranking tests
sql """ DROP TABLE IF EXISTS udwf_student_scores; """
sql """
CREATE TABLE udwf_student_scores (
student_id INT,
student_name VARCHAR(50),
class VARCHAR(20),
subject VARCHAR(20),
score INT,
exam_date DATE
) ENGINE=OLAP
DUPLICATE KEY(student_id)
DISTRIBUTED BY HASH(student_id) BUCKETS 2
PROPERTIES("replication_num" = "1");
"""
sql """
INSERT INTO udwf_student_scores VALUES
(1, 'Alice', 'ClassA', 'Math', 95, '2024-01-15'),
(1, 'Alice', 'ClassA', 'English', 88, '2024-01-15'),
(1, 'Alice', 'ClassA', 'Physics', 92, '2024-01-15'),
(2, 'Bob', 'ClassA', 'Math', 87, '2024-01-15'),
(2, 'Bob', 'ClassA', 'English', 90, '2024-01-15'),
(2, 'Bob', 'ClassA', 'Physics', 85, '2024-01-15'),
(3, 'Charlie', 'ClassA', 'Math', 92, '2024-01-15'),
(3, 'Charlie', 'ClassA', 'English', 85, '2024-01-15'),
(3, 'Charlie', 'ClassA', 'Physics', 88, '2024-01-15'),
(4, 'David', 'ClassB', 'Math', 78, '2024-01-15'),
(4, 'David', 'ClassB', 'English', 82, '2024-01-15'),
(4, 'David', 'ClassB', 'Physics', 80, '2024-01-15'),
(5, 'Eve', 'ClassB', 'Math', 90, '2024-01-15'),
(5, 'Eve', 'ClassB', 'English', 93, '2024-01-15'),
(5, 'Eve', 'ClassB', 'Physics', 89, '2024-01-15'),
(6, 'Frank', 'ClassB', 'Math', 85, '2024-01-15'),
(6, 'Frank', 'ClassB', 'English', 87, '2024-01-15'),
(6, 'Frank', 'ClassB', 'Physics', 91, '2024-01-15');
"""
// ========================================
// UDWF Definitions: Various window functions
// ========================================
// UDWF 1: Running Sum (Cumulative Sum)
sql """ DROP FUNCTION IF EXISTS py_udwf_running_sum(DOUBLE); """
sql """
CREATE AGGREGATE FUNCTION py_udwf_running_sum(DOUBLE)
RETURNS DOUBLE
PROPERTIES (
"type" = "PYTHON_UDF",
"symbol" = "RunningSumUDWF",
"runtime_version" = "${runtime_version}"
)
AS \$\$
class RunningSumUDWF:
def __init__(self):
self.sum = 0.0
@property
def aggregate_state(self):
return self.sum
def accumulate(self, value):
if value is not None:
self.sum += value
def merge(self, other_state):
self.sum += other_state
def finish(self):
return self.sum
\$\$;
"""
// UDWF 2: Running Average
sql """ DROP FUNCTION IF EXISTS py_udwf_running_avg(DOUBLE); """
sql """
CREATE AGGREGATE FUNCTION py_udwf_running_avg(DOUBLE)
RETURNS DOUBLE
PROPERTIES (
"type" = "PYTHON_UDF",
"symbol" = "RunningAvgUDWF",
"runtime_version" = "${runtime_version}"
)
AS \$\$
class RunningAvgUDWF:
def __init__(self):
self.sum = 0.0
self.count = 0
@property
def aggregate_state(self):
return (self.sum, self.count)
def accumulate(self, value):
if value is not None:
self.sum += value
self.count += 1
def merge(self, other_state):
other_sum, other_count = other_state
self.sum += other_sum
self.count += other_count
def finish(self):
if self.count == 0:
return None
return self.sum / self.count
\$\$;
"""
// UDWF 3: Running Count
sql """ DROP FUNCTION IF EXISTS py_udwf_running_count(INT); """
sql """
CREATE AGGREGATE FUNCTION py_udwf_running_count(INT)
RETURNS BIGINT
PROPERTIES (
"type" = "PYTHON_UDF",
"symbol" = "RunningCountUDWF",
"runtime_version" = "${runtime_version}"
)
AS \$\$
class RunningCountUDWF:
def __init__(self):
self.count = 0
@property
def aggregate_state(self):
return self.count
def accumulate(self, value):
if value is not None:
self.count += 1
def merge(self, other_state):
self.count += other_state
def finish(self):
return self.count
\$\$;
"""
// UDWF 4: Moving Average (for frame-based windows)
sql """ DROP FUNCTION IF EXISTS py_udwf_moving_avg(DOUBLE); """
sql """
CREATE AGGREGATE FUNCTION py_udwf_moving_avg(DOUBLE)
RETURNS DOUBLE
PROPERTIES (
"type" = "PYTHON_UDF",
"symbol" = "MovingAvgUDWF",
"runtime_version" = "${runtime_version}"
)
AS \$\$
class MovingAvgUDWF:
def __init__(self):
self.values = []
@property
def aggregate_state(self):
return self.values
def accumulate(self, value):
if value is not None:
self.values.append(value)
def merge(self, other_state):
if other_state:
self.values.extend(other_state)
def finish(self):
if not self.values:
return None
return sum(self.values) / len(self.values)
\$\$;
"""
// UDWF 5: Standard Deviation (for volatility analysis)
sql """ DROP FUNCTION IF EXISTS py_udwf_stddev(DOUBLE); """
sql """
CREATE AGGREGATE FUNCTION py_udwf_stddev(DOUBLE)
RETURNS DOUBLE
PROPERTIES (
"type" = "PYTHON_UDF",
"symbol" = "StdDevUDWF",
"runtime_version" = "${runtime_version}"
)
AS \$\$
import math
class StdDevUDWF:
def __init__(self):
self.values = []
@property
def aggregate_state(self):
return self.values
def accumulate(self, value):
if value is not None:
self.values.append(value)
def merge(self, other_state):
if other_state:
self.values.extend(other_state)
def finish(self):
if not self.values or len(self.values) < 2:
return None
mean = sum(self.values) / len(self.values)
variance = sum((x - mean) ** 2 for x in self.values) / len(self.values)
return math.sqrt(variance)
\$\$;
"""
// UDWF 6: Min Value
sql """ DROP FUNCTION IF EXISTS py_udwf_min(DOUBLE); """
sql """
CREATE AGGREGATE FUNCTION py_udwf_min(DOUBLE)
RETURNS DOUBLE
PROPERTIES (
"type" = "PYTHON_UDF",
"symbol" = "MinUDWF",
"runtime_version" = "${runtime_version}"
)
AS \$\$
class MinUDWF:
def __init__(self):
self.min_val = None
@property
def aggregate_state(self):
return self.min_val
def accumulate(self, value):
if value is not None:
if self.min_val is None or value < self.min_val:
self.min_val = value
def merge(self, other_state):
if other_state is not None:
if self.min_val is None or other_state < self.min_val:
self.min_val = other_state
def finish(self):
return self.min_val
\$\$;
"""
// UDWF 7: Max Value
sql """ DROP FUNCTION IF EXISTS py_udwf_max(DOUBLE); """
sql """
CREATE AGGREGATE FUNCTION py_udwf_max(DOUBLE)
RETURNS DOUBLE
PROPERTIES (
"type" = "PYTHON_UDF",
"symbol" = "MaxUDWF",
"runtime_version" = "${runtime_version}"
)
AS \$\$
class MaxUDWF:
def __init__(self):
self.max_val = None
@property
def aggregate_state(self):
return self.max_val
def accumulate(self, value):
if value is not None:
if self.max_val is None or value > self.max_val:
self.max_val = value
def merge(self, other_state):
if other_state is not None:
if self.max_val is None or other_state > self.max_val:
self.max_val = other_state
def finish(self):
return self.max_val
\$\$;
"""
// UDWF 8: First Value
sql """ DROP FUNCTION IF EXISTS py_udwf_first_value(DOUBLE); """
sql """
CREATE AGGREGATE FUNCTION py_udwf_first_value(DOUBLE)
RETURNS DOUBLE
PROPERTIES (
"type" = "PYTHON_UDF",
"symbol" = "FirstValueUDWF",
"runtime_version" = "${runtime_version}"
)
AS \$\$
class FirstValueUDWF:
def __init__(self):
self.first = None
self.has_value = False
@property
def aggregate_state(self):
return (self.first, self.has_value)
def accumulate(self, value):
if value is not None and not self.has_value:
self.first = value
self.has_value = True
def merge(self, other_state):
other_first, other_has_value = other_state
if other_has_value and not self.has_value:
self.first = other_first
self.has_value = True
def finish(self):
return self.first
\$\$;
"""
// UDWF 9: Last Value
sql """ DROP FUNCTION IF EXISTS py_udwf_last_value(DOUBLE); """
sql """
CREATE AGGREGATE FUNCTION py_udwf_last_value(DOUBLE)
RETURNS DOUBLE
PROPERTIES (
"type" = "PYTHON_UDF",
"symbol" = "LastValueUDWF",
"runtime_version" = "${runtime_version}"
)
AS \$\$
class LastValueUDWF:
def __init__(self):
self.last = None
@property
def aggregate_state(self):
return self.last
def accumulate(self, value):
if value is not None:
self.last = value
def merge(self, other_state):
if other_state is not None:
self.last = other_state
def finish(self):
return self.last
\$\$;
"""
// UDWF 10: Rank (Dense Rank implementation)
sql """ DROP FUNCTION IF EXISTS py_udwf_rank(INT); """
sql """
CREATE AGGREGATE FUNCTION py_udwf_rank(INT)
RETURNS BIGINT
PROPERTIES (
"type" = "PYTHON_UDF",
"symbol" = "RankUDWF",
"runtime_version" = "${runtime_version}"
)
AS \$\$
class RankUDWF:
def __init__(self):
self.count = 0
@property
def aggregate_state(self):
return self.count
def accumulate(self, value):
if value is not None:
self.count += 1
def merge(self, other_state):
self.count += other_state
def finish(self):
return self.count
\$\$;
"""
// ========================================
// Test Category 1: Basic Window Functions with PARTITION BY
// ========================================
// Test 1.1: Simple PARTITION BY with running sum
qt_test_partition_by_running_sum """
SELECT
order_id,
region,
sales_amount,
py_udwf_running_sum(sales_amount) OVER (PARTITION BY region ORDER BY order_id) as region_running_sum
FROM udwf_sales
ORDER BY region, order_id;
"""
// Test 1.2: PARTITION BY with running average
qt_test_partition_by_running_avg """
SELECT
order_id,
category,
sales_amount,
py_udwf_running_avg(sales_amount) OVER (PARTITION BY category ORDER BY order_date) as category_running_avg
FROM udwf_sales
ORDER BY category, order_date, order_id;
"""
// Test 1.3: PARTITION BY with running count
qt_test_partition_by_running_count """
SELECT
order_id,
salesperson,
quantity,
py_udwf_running_count(quantity) OVER (PARTITION BY salesperson ORDER BY order_id) as sales_count
FROM udwf_sales
ORDER BY salesperson, order_id;
"""
// Test 1.4: Multiple PARTITION BY columns
qt_test_multi_partition_columns """
SELECT
order_id,
region,
category,
sales_amount,
py_udwf_running_sum(sales_amount) OVER (PARTITION BY region, category ORDER BY order_id) as segment_sum
FROM udwf_sales
ORDER BY region, category, order_id;
"""
// ========================================
// Test Category 2: Window Functions with ORDER BY only
// ========================================
// Test 2.1: ORDER BY with cumulative sum
qt_test_order_by_cumulative_sum """
SELECT
order_id,
order_date,
sales_amount,
py_udwf_running_sum(sales_amount) OVER (ORDER BY order_date, order_id) as cumulative_sales
FROM udwf_sales
ORDER BY order_date, order_id;
"""
// Test 2.2: ORDER BY with cumulative average
qt_test_order_by_cumulative_avg """
SELECT
order_id,
order_date,
sales_amount,
py_udwf_running_avg(sales_amount) OVER (ORDER BY order_date) as cumulative_avg
FROM udwf_sales
ORDER BY order_date, order_id;
"""
// Test 2.3: ORDER BY DESC
qt_test_order_by_desc """
SELECT
order_id,
sales_amount,
py_udwf_running_sum(sales_amount) OVER (ORDER BY sales_amount DESC) as sum_by_amount_desc
FROM udwf_sales
ORDER BY sales_amount DESC, order_id;
"""
// ========================================
// Test Category 3: Window Functions with Frame Specifications
// ========================================
// Test 3.1: ROWS BETWEEN frame (moving average - 3 row window)
qt_test_rows_between_moving_avg """
SELECT
order_id,
order_date,
sales_amount,
py_udwf_moving_avg(sales_amount) OVER (
ORDER BY order_date
ROWS BETWEEN 2 PRECEDING AND CURRENT ROW
) as moving_avg_3
FROM udwf_sales
ORDER BY order_date, order_id;
"""
// Test 3.2: ROWS BETWEEN with partition
qt_test_rows_between_with_partition """
SELECT
order_id,
region,
sales_amount,
py_udwf_moving_avg(sales_amount) OVER (
PARTITION BY region
ORDER BY order_id
ROWS BETWEEN 1 PRECEDING AND 1 FOLLOWING
) as moving_avg_region
FROM udwf_sales
ORDER BY region, order_id;
"""
// Test 3.3: ROWS BETWEEN UNBOUNDED PRECEDING
qt_test_rows_unbounded_preceding """
SELECT
order_id,
category,
sales_amount,
py_udwf_running_sum(sales_amount) OVER (
PARTITION BY category
ORDER BY order_id
ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW
) as category_cumsum
FROM udwf_sales
ORDER BY category, order_id;
"""
// Test 3.4: ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING
qt_test_rows_unbounded_following """
SELECT
order_id,
region,
sales_amount,
py_udwf_running_sum(sales_amount) OVER (
PARTITION BY region
ORDER BY order_id
ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING
) as remaining_sum
FROM udwf_sales
ORDER BY region, order_id;
"""
// ========================================
// Test Category 4: Multiple Window Functions in Single Query
// ========================================
// Test 4.1: Multiple UDWFs with same partition
qt_test_multiple_udwf_same_partition """
SELECT
order_id,
region,
sales_amount,
py_udwf_running_sum(sales_amount) OVER (PARTITION BY region ORDER BY order_id) as running_sum,
py_udwf_running_avg(sales_amount) OVER (PARTITION BY region ORDER BY order_id) as running_avg,
py_udwf_running_count(quantity) OVER (PARTITION BY region ORDER BY order_id) as running_count
FROM udwf_sales
ORDER BY region, order_id;
"""
// Test 4.2: Multiple UDWFs with different partitions
qt_test_multiple_udwf_diff_partition """
SELECT
order_id,
region,
category,
sales_amount,
py_udwf_running_sum(sales_amount) OVER (PARTITION BY region ORDER BY order_id) as region_sum,
py_udwf_running_sum(sales_amount) OVER (PARTITION BY category ORDER BY order_id) as category_sum,
py_udwf_running_sum(sales_amount) OVER (ORDER BY order_id) as total_sum
FROM udwf_sales
ORDER BY order_id;
"""
// Test 4.3: Mix of UDWFs and built-in functions
qt_test_mix_udwf_builtin """
SELECT
order_id,
region,
sales_amount,
py_udwf_running_sum(sales_amount) OVER (PARTITION BY region ORDER BY order_id) as py_sum,
SUM(sales_amount) OVER (PARTITION BY region ORDER BY order_id) as builtin_sum,
py_udwf_running_avg(sales_amount) OVER (PARTITION BY region ORDER BY order_id) as py_avg,
AVG(sales_amount) OVER (PARTITION BY region ORDER BY order_id) as builtin_avg
FROM udwf_sales
ORDER BY region, order_id;
"""
// ========================================
// Test Category 5: Statistical Analysis Functions
// ========================================
// Test 5.1: Standard deviation by partition
qt_test_stddev_by_partition """
SELECT
symbol,
trade_time,
price,
py_udwf_stddev(price) OVER (
PARTITION BY symbol
ORDER BY trade_time
ROWS BETWEEN 2 PRECEDING AND CURRENT ROW
) as price_volatility
FROM udwf_stock_prices
ORDER BY symbol, trade_time;
"""
// Test 5.2: Min and Max in moving window
qt_test_min_max_moving_window """
SELECT
symbol,
trade_time,
price,
py_udwf_min(price) OVER (
PARTITION BY symbol
ORDER BY trade_time
ROWS BETWEEN 2 PRECEDING AND CURRENT ROW
) as rolling_min,
py_udwf_max(price) OVER (
PARTITION BY symbol
ORDER BY trade_time
ROWS BETWEEN 2 PRECEDING AND CURRENT ROW
) as rolling_max
FROM udwf_stock_prices
ORDER BY symbol, trade_time;
"""
// Test 5.3: First and Last value in window
qt_test_first_last_value """
SELECT
symbol,
trade_time,
price,
py_udwf_first_value(price) OVER (
PARTITION BY symbol
ORDER BY trade_time
) as opening_price,
py_udwf_last_value(price) OVER (
PARTITION BY symbol
ORDER BY trade_time
ROWS BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING
) as closing_price
FROM udwf_stock_prices
ORDER BY symbol, trade_time;
"""
// ========================================
// Test Category 6: Complex Analytical Queries
// ========================================
// Test 6.1: Category-based cumulative sales analysis
qt_test_complex_growth_analysis """
SELECT
region,
category,
total_sales,
py_udwf_running_sum(total_sales) OVER (
PARTITION BY region
ORDER BY category
) as cumulative_by_category
FROM (
SELECT
region,
category,
SUM(sales_amount) as total_sales
FROM udwf_sales
GROUP BY region, category
) t
ORDER BY region, category;
"""
// Test 6.2: Top-N analysis with ranking
qt_test_topn_analysis """
SELECT
student_name,
class,
subject,
score,
py_udwf_rank(score) OVER (
PARTITION BY class, subject
ORDER BY score DESC
) as rank_in_subject
FROM udwf_student_scores
ORDER BY class, subject, score DESC;
"""
// Test 6.3: Percentile calculation using window
qt_test_percentile_analysis """
SELECT
class,
subject,
score,
py_udwf_running_count(score) OVER (
PARTITION BY class, subject
ORDER BY score
) as count_below_or_equal
FROM udwf_student_scores
ORDER BY class, subject, score;
"""
// ========================================
// Test Category 7: Edge Cases and Null Handling
// ========================================
// Test 7.1: Empty partition
qt_test_empty_partition """
SELECT
order_id,
region,
sales_amount,
py_udwf_running_sum(sales_amount) OVER (
PARTITION BY product
ORDER BY order_id
) as product_sum
FROM udwf_sales
WHERE region = 'North'
ORDER BY product, order_id;
"""
// Test 7.2: Single row partition
sql """ DROP TABLE IF EXISTS udwf_single_row; """
sql """
CREATE TABLE udwf_single_row (
id INT,
category VARCHAR(10),
value DOUBLE
) ENGINE=OLAP
DUPLICATE KEY(id)
DISTRIBUTED BY HASH(id) BUCKETS 1
PROPERTIES("replication_num" = "1");
"""
sql """
INSERT INTO udwf_single_row VALUES
(1, 'A', 100.0),
(2, 'B', 200.0),
(3, 'C', 300.0);
"""
qt_test_single_row_partition """
SELECT
id,
category,
value,
py_udwf_running_sum(value) OVER (PARTITION BY category ORDER BY id) as cat_sum
FROM udwf_single_row
ORDER BY id;
"""
// Test 7.3: NULL values handling
sql """ DROP TABLE IF EXISTS udwf_with_nulls; """
sql """
CREATE TABLE udwf_with_nulls (
id INT,
category VARCHAR(10),
value DOUBLE
) ENGINE=OLAP
DUPLICATE KEY(id)
DISTRIBUTED BY HASH(id) BUCKETS 1
PROPERTIES("replication_num" = "1");
"""
sql """
INSERT INTO udwf_with_nulls VALUES
(1, 'A', 100.0),
(2, 'A', NULL),
(3, 'A', 200.0),
(4, 'A', NULL),
(5, 'A', 300.0),
(6, 'B', NULL),
(7, 'B', 150.0),
(8, 'B', NULL);
"""
qt_test_null_values """
SELECT
id,
category,
value,
py_udwf_running_sum(value) OVER (PARTITION BY category ORDER BY id) as sum_ignore_null,
py_udwf_running_count(value) OVER (PARTITION BY category ORDER BY id) as count_non_null,
py_udwf_running_avg(value) OVER (PARTITION BY category ORDER BY id) as avg_ignore_null
FROM udwf_with_nulls
ORDER BY category, id;
"""
// Test 7.4: All NULL values in partition
sql """ DROP TABLE IF EXISTS udwf_all_nulls; """
sql """
CREATE TABLE udwf_all_nulls (
id INT,
category VARCHAR(10),
value DOUBLE
) ENGINE=OLAP
DUPLICATE KEY(id)
DISTRIBUTED BY HASH(id) BUCKETS 1
PROPERTIES("replication_num" = "1");
"""
sql """
INSERT INTO udwf_all_nulls VALUES
(1, 'A', NULL),
(2, 'A', NULL),
(3, 'B', 100.0);
"""
qt_test_all_nulls_partition """
SELECT
id,
category,
value,
py_udwf_running_sum(value) OVER (PARTITION BY category ORDER BY id) as sum_result,
py_udwf_running_avg(value) OVER (PARTITION BY category ORDER BY id) as avg_result
FROM udwf_all_nulls
ORDER BY category, id;
"""
// ========================================
// Test Category 8: Performance and Scalability
// ========================================
// Test 8.1: Large partition test (high cardinality)
qt_test_large_partition """
SELECT
order_id,
salesperson,
sales_amount,
py_udwf_running_sum(sales_amount) OVER (
PARTITION BY salesperson
ORDER BY order_id
) as salesperson_total
FROM udwf_sales
ORDER BY salesperson, order_id;
"""
// Test 8.2: Multiple complex windows in one query
qt_test_multiple_complex_windows """
SELECT
order_id,
region,
category,
product,
sales_amount,
py_udwf_running_sum(sales_amount) OVER (PARTITION BY region ORDER BY order_id) as region_sum,
py_udwf_running_avg(sales_amount) OVER (PARTITION BY region ORDER BY order_id) as region_avg,
py_udwf_min(sales_amount) OVER (PARTITION BY category ORDER BY order_id ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW) as category_min,
py_udwf_max(sales_amount) OVER (PARTITION BY category ORDER BY order_id ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW) as category_max,
py_udwf_running_count(quantity) OVER (PARTITION BY product ORDER BY order_id) as product_count
FROM udwf_sales
ORDER BY order_id;
"""
// ========================================
// Test Category 9: Data Type Coverage
// ========================================
// Test 9.1: INT type
qt_test_int_type """
SELECT
order_id,
region,
quantity,
py_udwf_running_count(quantity) OVER (PARTITION BY region ORDER BY order_id) as qty_count
FROM udwf_sales
ORDER BY region, order_id;
"""
// Test 9.2: DECIMAL type
qt_test_decimal_type """
SELECT
order_id,
category,
sales_amount,
py_udwf_running_sum(sales_amount) OVER (PARTITION BY category ORDER BY order_id) as decimal_sum
FROM udwf_sales
ORDER BY category, order_id;
"""
// Test 9.3: DOUBLE type
qt_test_double_type """
SELECT
trade_id,
symbol,
price,
py_udwf_running_avg(price) OVER (PARTITION BY symbol ORDER BY trade_time) as avg_price
FROM udwf_stock_prices
ORDER BY symbol, trade_time;
"""
// ========================================
// Test Category 10: Subquery and CTE with Window Functions
// ========================================
// Test 10.1: Window function in subquery
qt_test_window_in_subquery """
SELECT
region,
AVG(running_sum) as avg_running_sum
FROM (
SELECT
region,
order_id,
py_udwf_running_sum(sales_amount) OVER (
PARTITION BY region
ORDER BY order_id
) as running_sum
FROM udwf_sales
) t
GROUP BY region
ORDER BY region;
"""
// Test 10.2: Window function with CTE
qt_test_window_with_cte """
WITH regional_sales AS (
SELECT
region,
order_id,
sales_amount,
py_udwf_running_sum(sales_amount) OVER (
PARTITION BY region
ORDER BY order_id
) as cumulative_sales
FROM udwf_sales
)
SELECT
region,
MAX(cumulative_sales) as max_cumulative
FROM regional_sales
GROUP BY region
ORDER BY region;
"""
// Test 10.3: Nested window functions (window over window result)
qt_test_nested_windows """
SELECT
region,
order_id,
sales_amount,
running_sum,
py_udwf_running_avg(running_sum) OVER (
PARTITION BY region
ORDER BY order_id
) as avg_of_running_sum
FROM (
SELECT
region,
order_id,
sales_amount,
py_udwf_running_sum(sales_amount) OVER (
PARTITION BY region
ORDER BY order_id
) as running_sum
FROM udwf_sales
) t
ORDER BY region, order_id;
"""
// ========================================
// Test Category 11: JOIN with Window Functions
// ========================================
// Test 11.1: Window function after JOIN
sql """ DROP TABLE IF EXISTS udwf_customers; """
sql """
CREATE TABLE udwf_customers (
salesperson VARCHAR(50),
customer_level VARCHAR(20),
commission_rate DOUBLE
) ENGINE=OLAP
DUPLICATE KEY(salesperson)
DISTRIBUTED BY HASH(salesperson) BUCKETS 1
PROPERTIES("replication_num" = "1");
"""
sql """
INSERT INTO udwf_customers VALUES
('Alice', 'Gold', 0.15),
('Bob', 'Silver', 0.10),
('Charlie', 'Gold', 0.15),
('David', 'Bronze', 0.08),
('Eve', 'Gold', 0.15),
('Frank', 'Silver', 0.10),
('Grace', 'Gold', 0.15),
('Henry', 'Bronze', 0.08);
"""
qt_test_window_after_join """
SELECT
s.order_id,
s.salesperson,
c.customer_level,
s.sales_amount,
py_udwf_running_sum(s.sales_amount) OVER (
PARTITION BY c.customer_level
ORDER BY s.order_id
) as level_running_sum
FROM udwf_sales s
JOIN udwf_customers c ON s.salesperson = c.salesperson
ORDER BY c.customer_level, s.order_id;
"""
// ========================================
// Cleanup
// ========================================
sql """ DROP FUNCTION IF EXISTS py_udwf_running_sum(DOUBLE); """
sql """ DROP FUNCTION IF EXISTS py_udwf_running_avg(DOUBLE); """
sql """ DROP FUNCTION IF EXISTS py_udwf_running_count(INT); """
sql """ DROP FUNCTION IF EXISTS py_udwf_moving_avg(DOUBLE); """
sql """ DROP FUNCTION IF EXISTS py_udwf_stddev(DOUBLE); """
sql """ DROP FUNCTION IF EXISTS py_udwf_min(DOUBLE); """
sql """ DROP FUNCTION IF EXISTS py_udwf_max(DOUBLE); """
sql """ DROP FUNCTION IF EXISTS py_udwf_first_value(DOUBLE); """
sql """ DROP FUNCTION IF EXISTS py_udwf_last_value(DOUBLE); """
sql """ DROP FUNCTION IF EXISTS py_udwf_rank(INT); """
sql """ DROP TABLE IF EXISTS udwf_sales; """
sql """ DROP TABLE IF EXISTS udwf_stock_prices; """
sql """ DROP TABLE IF EXISTS udwf_student_scores; """
sql """ DROP TABLE IF EXISTS udwf_single_row; """
sql """ DROP TABLE IF EXISTS udwf_with_nulls; """
sql """ DROP TABLE IF EXISTS udwf_all_nulls; """
sql """ DROP TABLE IF EXISTS udwf_customers; """
} finally {
// Ensure cleanup even if tests fail
try {
sql """ DROP FUNCTION IF EXISTS py_udwf_running_sum(DOUBLE); """
sql """ DROP FUNCTION IF EXISTS py_udwf_running_avg(DOUBLE); """
sql """ DROP FUNCTION IF EXISTS py_udwf_running_count(INT); """
sql """ DROP FUNCTION IF EXISTS py_udwf_moving_avg(DOUBLE); """
sql """ DROP FUNCTION IF EXISTS py_udwf_stddev(DOUBLE); """
sql """ DROP FUNCTION IF EXISTS py_udwf_min(DOUBLE); """
sql """ DROP FUNCTION IF EXISTS py_udwf_max(DOUBLE); """
sql """ DROP FUNCTION IF EXISTS py_udwf_first_value(DOUBLE); """
sql """ DROP FUNCTION IF EXISTS py_udwf_last_value(DOUBLE); """
sql """ DROP FUNCTION IF EXISTS py_udwf_rank(INT); """
sql """ DROP TABLE IF EXISTS udwf_sales; """
sql """ DROP TABLE IF EXISTS udwf_stock_prices; """
sql """ DROP TABLE IF EXISTS udwf_student_scores; """
sql """ DROP TABLE IF EXISTS udwf_single_row; """
sql """ DROP TABLE IF EXISTS udwf_with_nulls; """
sql """ DROP TABLE IF EXISTS udwf_all_nulls; """
sql """ DROP TABLE IF EXISTS udwf_customers; """
} catch (Exception e) {
// Ignore cleanup errors
}
}
}