blob: 9ce214c59305a6b36af32d16bb1687a6992e43a1 [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_pythonudf_drop", "nonConcurrent") {
def runtime_version = getPythonUdfRuntimeVersion()
def zipA = """${context.file.parent}/udf_scripts/python_udf_drop_a/python_udf_drop_test.zip"""
def zipB = """${context.file.parent}/udf_scripts/python_udf_drop_b/python_udf_drop_test.zip"""
def localDorisHome = System.getenv("DORIS_HOME")
def localUdfRoot = localDorisHome != null ? "${localDorisHome}/lib/udf" : "/tmp"
def backendId_to_backendIP = [:]
def backendId_to_backendHttpPort = [:]
getBackendIpHttpPort(backendId_to_backendIP, backendId_to_backendHttpPort)
def execOnBackend = { be_ip, localCmd, remoteCmd ->
if (be_ip == "127.0.0.1" || be_ip == "localhost") {
cmd(localCmd)
} else {
sshExec("root", be_ip, remoteCmd, false)
}
}
scp_udf_file_to_all_be(zipA)
scp_udf_file_to_all_be(zipB)
sql """DROP TABLE IF EXISTS py_udf_drop_tbl"""
sql """
CREATE TABLE py_udf_drop_tbl (
id INT
) ENGINE=OLAP
DUPLICATE KEY(id)
DISTRIBUTED BY HASH(id) BUCKETS 1
PROPERTIES("replication_num" = "1");
"""
sql """INSERT INTO py_udf_drop_tbl VALUES (1), (2), (3);"""
try {
// Case 1: simple drop should make subsequent call fail
sql """DROP FUNCTION IF EXISTS py_drop_once(INT)"""
sql """
CREATE FUNCTION py_drop_once(INT) RETURNS INT PROPERTIES (
"type" = "PYTHON_UDF",
"file" = "file://${zipA}",
"symbol" = "drop_udf.evaluate",
"runtime_version" = "${runtime_version}"
)
"""
qt_py_udf_drop_1 """SELECT py_drop_once(10);"""
try_sql("DROP FUNCTION IF EXISTS py_drop_once(INT);")
test {
sql """SELECT py_drop_once(10);"""
exception "Can not found function"
}
// Case 2: same module name, different file paths
sql """DROP FUNCTION IF EXISTS py_drop_a(INT)"""
sql """DROP FUNCTION IF EXISTS py_drop_b(INT)"""
sql """
CREATE FUNCTION py_drop_a(INT) RETURNS INT PROPERTIES (
"type" = "PYTHON_UDF",
"file" = "file://${zipA}",
"symbol" = "drop_udf.evaluate",
"runtime_version" = "${runtime_version}"
)
"""
sql """
CREATE FUNCTION py_drop_b(INT) RETURNS INT PROPERTIES (
"type" = "PYTHON_UDF",
"file" = "file://${zipB}",
"symbol" = "drop_udf.evaluate",
"runtime_version" = "${runtime_version}"
)
"""
qt_py_udf_drop_2 """SELECT py_drop_a(5), py_drop_b(5);"""
try_sql("DROP FUNCTION IF EXISTS py_drop_b(INT);")
test {
sql """SELECT py_drop_b(5);"""
exception "Can not found function"
}
qt_py_udf_drop_3 """SELECT py_drop_a(7);"""
try_sql("DROP FUNCTION IF EXISTS py_drop_a(INT);")
test {
sql """SELECT py_drop_a(1);"""
exception "Can not found function"
}
// Case 3: kill Python servers between two queries, next client handshake should recover
sql """DROP FUNCTION IF EXISTS py_drop_reconnect(INT)"""
sql """
CREATE FUNCTION py_drop_reconnect(INT) RETURNS INT PROPERTIES (
"type" = "PYTHON_UDF",
"file" = "file://${zipA}",
"symbol" = "drop_udf.evaluate",
"runtime_version" = "${runtime_version}"
)
"""
qt_py_udf_drop_4 """SELECT py_drop_reconnect(31);"""
backendId_to_backendIP.values().each { be_ip ->
execOnBackend(
be_ip,
"pkill -f 'python_server.py grpc+unix:///tmp/doris_python_udf' || true",
"pkill -f 'python_server.py grpc+unix:///tmp/doris_python_udf' || true")
}
qt_py_udf_drop_5 """SELECT py_drop_reconnect(32);"""
try_sql("DROP FUNCTION IF EXISTS py_drop_reconnect(INT);")
// Case 4: recreating the same signature must use the new inline function body.
sql """DROP FUNCTION IF EXISTS py_drop_recreate(INT)"""
sql """
CREATE FUNCTION py_drop_recreate(INT)
RETURNS INT
PROPERTIES (
"type" = "PYTHON_UDF",
"symbol" = "evaluate",
"runtime_version" = "${runtime_version}",
"always_nullable" = "true"
)
AS \$\$
def evaluate(x):
if x is None:
return None
return x + 1
\$\$
"""
def recreateOldResult = sql """SELECT py_drop_recreate(10);"""
assert recreateOldResult[0][0] == 11
sql """DROP FUNCTION IF EXISTS py_drop_recreate(INT)"""
sql """
CREATE FUNCTION py_drop_recreate(INT)
RETURNS INT
PROPERTIES (
"type" = "PYTHON_UDF",
"symbol" = "evaluate",
"runtime_version" = "${runtime_version}",
"always_nullable" = "true"
)
AS \$\$
def evaluate(x):
if x is None:
return None
return x + 999
\$\$
"""
def recreateNewResult = sql """SELECT py_drop_recreate(10);"""
assert recreateNewResult[0][0] == 1009
sql """DROP FUNCTION IF EXISTS py_drop_recreate(INT)"""
// Case 5: dropping a database must also clear Nereids UDF registry.
// SHOW FUNCTIONS reads catalog metadata, while SELECT resolves from FunctionRegistry.
// Without registry cleanup, SELECT could still bind the stale x + 1 inline UDF
// after the database had been dropped and recreated.
def originalDb = sql("SELECT DATABASE()")[0][0]
def registryDb = "${originalDb}_registry_cleanup"
try {
sql """DROP DATABASE IF EXISTS ${registryDb} FORCE"""
sql """CREATE DATABASE ${registryDb}"""
sql """USE ${registryDb}"""
sql """
CREATE FUNCTION py_drop_db_registry(INT)
RETURNS INT
PROPERTIES (
"type" = "PYTHON_UDF",
"symbol" = "evaluate",
"runtime_version" = "${runtime_version}",
"always_nullable" = "true"
)
AS \$\$
def evaluate(x):
if x is None:
return None
return x + 1
\$\$
"""
def oldResult = sql """SELECT py_drop_db_registry(10);"""
assert oldResult[0][0] == 11
sql """DROP DATABASE ${registryDb} FORCE"""
sql """CREATE DATABASE ${registryDb}"""
sql """USE ${registryDb}"""
def functions = sql """SHOW FUNCTIONS LIKE 'py_drop_db_registry'"""
assert functions.isEmpty()
test {
sql """SELECT py_drop_db_registry(10);"""
exception "Can not found function"
}
sql """
CREATE FUNCTION py_drop_db_registry(INT)
RETURNS INT
PROPERTIES (
"type" = "PYTHON_UDF",
"symbol" = "evaluate",
"runtime_version" = "${runtime_version}",
"always_nullable" = "true"
)
AS \$\$
def evaluate(x):
if x is None:
return None
return x + 999
\$\$
"""
def rebuiltResult = sql """SELECT py_drop_db_registry(10);"""
assert rebuiltResult[0][0] == 1009
} finally {
sql """USE ${originalDb}"""
try_sql("DROP DATABASE IF EXISTS ${registryDb} FORCE")
}
} finally {
try_sql("DROP FUNCTION IF EXISTS py_drop_once(INT);")
try_sql("DROP FUNCTION IF EXISTS py_drop_a(INT);")
try_sql("DROP FUNCTION IF EXISTS py_drop_b(INT);")
try_sql("DROP FUNCTION IF EXISTS py_drop_reconnect(INT);")
try_sql("DROP FUNCTION IF EXISTS py_drop_recreate(INT);")
}
}