blob: a66317b34f1576d089dc9f1ec6a2cb7acc6dfc69 [file] [log] [blame]
// 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_pythonudf_always_nullable") {
// Test different configurations of always_nullable parameter
def runtime_version = "3.10.12"
try {
// Test 1: always_nullable = true (default value)
sql """ DROP FUNCTION IF EXISTS py_nullable_true(INT); """
sql """
CREATE FUNCTION py_nullable_true(INT)
RETURNS INT
PROPERTIES (
"type" = "PYTHON_UDF",
"symbol" = "evaluate",
"always_nullable" = "true",
"runtime_version" = "${runtime_version}"
)
AS \$\$
def evaluate(x):
if x is None:
return None
if x < 0:
return None
return x * 2
\$\$;
"""
qt_select_nullable_true_normal """ SELECT py_nullable_true(10) AS result; """
qt_select_nullable_true_null """ SELECT py_nullable_true(NULL) AS result; """
qt_select_nullable_true_negative """ SELECT py_nullable_true(-5) AS result; """
// Test 2: always_nullable = false
sql """ DROP FUNCTION IF EXISTS py_nullable_false(INT); """
sql """
CREATE FUNCTION py_nullable_false(INT)
RETURNS INT
PROPERTIES (
"type" = "PYTHON_UDF",
"symbol" = "evaluate",
"always_nullable" = "false",
"runtime_version" = "${runtime_version}"
)
AS \$\$
def evaluate(x):
if x is None:
return 0
return x * 2
\$\$;
"""
qt_select_nullable_false_normal """ SELECT py_nullable_false(10) AS result; """
qt_select_nullable_false_null """ SELECT py_nullable_false(NULL) AS result; """
// Test 3: always_nullable = false but function returns None
// This tests the edge case where the function violates the always_nullable contract
sql """ DROP FUNCTION IF EXISTS py_nullable_false_returns_none(INT); """
sql """
CREATE FUNCTION py_nullable_false_returns_none(INT)
RETURNS INT
PROPERTIES (
"type" = "PYTHON_UDF",
"symbol" = "evaluate",
"always_nullable" = "false",
"runtime_version" = "${runtime_version}"
)
AS \$\$
def evaluate(x):
if x < 0:
return None # Returns None even though always_nullable is false
return x * 2
\$\$;
"""
qt_select_nullable_false_returns_none_normal """ SELECT py_nullable_false_returns_none(10) AS result; """
test {
sql """ SELECT py_nullable_false_returns_none(-5) AS result; """
exception "but the return type is not nullable, please check the always_nullable property in create function statement, it should be true"
}
// Test 4: Test nullable behavior on table data
sql """ DROP TABLE IF EXISTS nullable_test_table; """
sql """
CREATE TABLE nullable_test_table (
id INT,
value INT
) ENGINE=OLAP
DUPLICATE KEY(id)
DISTRIBUTED BY HASH(id) BUCKETS 1
PROPERTIES("replication_num" = "1");
"""
sql """
INSERT INTO nullable_test_table VALUES
(1, 10),
(2, NULL),
(3, -5),
(4, 0),
(5, 100);
"""
qt_select_table_nullable_true """
SELECT
id,
value,
py_nullable_true(value) AS result
FROM nullable_test_table
ORDER BY id;
"""
qt_select_table_nullable_false """
SELECT
id,
value,
py_nullable_false(value) AS result
FROM nullable_test_table
ORDER BY id;
"""
test {
sql """
SELECT
id,
value,
py_nullable_false_returns_none(value) AS result
FROM nullable_test_table
ORDER BY id;
"""
exception "'<' not supported between instances of 'NoneType' and 'int'"
}
// Test 5: Nullable test for string type
sql """ DROP FUNCTION IF EXISTS py_string_nullable(STRING); """
sql """
CREATE FUNCTION py_string_nullable(STRING)
RETURNS STRING
PROPERTIES (
"type" = "PYTHON_UDF",
"symbol" = "evaluate",
"always_nullable" = "true",
"runtime_version" = "${runtime_version}"
)
AS \$\$
def evaluate(s):
if s is None or s == "":
return None
return s.upper()
\$\$;
"""
qt_select_string_nullable """ SELECT py_string_nullable('hello') AS result; """
qt_select_string_nullable_null """ SELECT py_string_nullable(NULL) AS result; """
qt_select_string_nullable_empty """ SELECT py_string_nullable('') AS result; """
} finally {
try_sql("DROP FUNCTION IF EXISTS py_nullable_true(INT);")
try_sql("DROP FUNCTION IF EXISTS py_nullable_false(INT);")
try_sql("DROP FUNCTION IF EXISTS py_nullable_false_returns_none(INT);")
try_sql("DROP FUNCTION IF EXISTS py_string_nullable(STRING);")
try_sql("DROP TABLE IF EXISTS nullable_test_table;")
}
}