From 49757dc80d5aec1213045a6cce8bed589a80ad92 Mon Sep 17 00:00:00 2001 From: Stefan Wang <1fannnw@gmail.com> Date: Tue, 8 Sep 2026 12:46:11 -0700 Subject: [PATCH 1/4] GH-51228: [Python] Reject invalid tabular function registry Validate call_tabular_function registry inputs before casting them to the native FunctionRegistry pointer. Signed-off-by: Stefan Wang <1fannnw@gmail.com> Signed-off-by: 1fanwang <1fannnw@gmail.com> --- python/pyarrow/_compute.pyx | 2 ++ python/pyarrow/tests/test_compute.py | 19 +++++++++++++++++++ 2 files changed, 21 insertions(+) diff --git a/python/pyarrow/_compute.pyx b/python/pyarrow/_compute.pyx index 1c0c779f6037..3390abaedb05 100644 --- a/python/pyarrow/_compute.pyx +++ b/python/pyarrow/_compute.pyx @@ -3545,6 +3545,8 @@ def call_tabular_function(function_name, args=None, func_registry=None): c_func_name = tobytes(function_name) if func_registry is None: c_func_registry = NULL + elif not isinstance(func_registry, FunctionRegistry): + raise TypeError("func_registry must be a FunctionRegistry") else: c_func_registry = (func_registry).registry if args is None: diff --git a/python/pyarrow/tests/test_compute.py b/python/pyarrow/tests/test_compute.py index 797fbc220ec3..24c851e651a8 100644 --- a/python/pyarrow/tests/test_compute.py +++ b/python/pyarrow/tests/test_compute.py @@ -25,6 +25,7 @@ import os import pytest import random +import subprocess import sys import textwrap @@ -251,6 +252,24 @@ def test_list_functions(): assert "add" in pc.list_functions() +def test_call_tabular_function_rejects_invalid_registry(): + code = """if 1: + import pyarrow.compute as pc + + try: + pc.call_tabular_function("", None, 1) + except TypeError as exc: + assert str(exc) == "func_registry must be a FunctionRegistry" + else: + raise AssertionError("expected TypeError") + """ + res = subprocess.run([sys.executable, "-c", code], + universal_newlines=True, stderr=subprocess.PIPE) + if res.returncode != 0: + print(res.stderr, file=sys.stderr) + res.check_returncode() + + def _check_get_function(name, expected_func_cls, expected_ker_cls, min_num_kernels=1): func = pc.get_function(name) From 590a742c7171b75c3b0759687cd5520c5e97df85 Mon Sep 17 00:00:00 2001 From: 1fanwang <1fannnw@gmail.com> Date: Tue, 8 Sep 2026 15:37:10 -0700 Subject: [PATCH 2/4] GH-51228: Mark the subprocess test with pytest.mark.processes The test spawns a subprocess, which Emscripten does not support. Without the marker the test runs there anyway and fails. Signed-off-by: 1fanwang <1fannnw@gmail.com> --- python/pyarrow/tests/test_compute.py | 1 + 1 file changed, 1 insertion(+) diff --git a/python/pyarrow/tests/test_compute.py b/python/pyarrow/tests/test_compute.py index 24c851e651a8..b31c1dec56e3 100644 --- a/python/pyarrow/tests/test_compute.py +++ b/python/pyarrow/tests/test_compute.py @@ -252,6 +252,7 @@ def test_list_functions(): assert "add" in pc.list_functions() +@pytest.mark.processes def test_call_tabular_function_rejects_invalid_registry(): code = """if 1: import pyarrow.compute as pc From 29192c533f03b099187cbf311a055e8cc079b545 Mon Sep 17 00:00:00 2001 From: 1fanwang <1fannnw@gmail.com> Date: Thu, 10 Sep 2026 04:53:56 -0700 Subject: [PATCH 3/4] GH-51228: Assert the type error with pytest.raises The subprocess wrapper guarded against the crash this change removes, so the in-process form reads better now. Signed-off-by: 1fanwang <1fannnw@gmail.com> --- python/pyarrow/tests/test_compute.py | 18 ++---------------- 1 file changed, 2 insertions(+), 16 deletions(-) diff --git a/python/pyarrow/tests/test_compute.py b/python/pyarrow/tests/test_compute.py index b31c1dec56e3..36724efd7b61 100644 --- a/python/pyarrow/tests/test_compute.py +++ b/python/pyarrow/tests/test_compute.py @@ -25,7 +25,6 @@ import os import pytest import random -import subprocess import sys import textwrap @@ -252,23 +251,10 @@ def test_list_functions(): assert "add" in pc.list_functions() -@pytest.mark.processes def test_call_tabular_function_rejects_invalid_registry(): - code = """if 1: - import pyarrow.compute as pc - - try: + with pytest.raises(TypeError, + match="func_registry must be a FunctionRegistry"): pc.call_tabular_function("", None, 1) - except TypeError as exc: - assert str(exc) == "func_registry must be a FunctionRegistry" - else: - raise AssertionError("expected TypeError") - """ - res = subprocess.run([sys.executable, "-c", code], - universal_newlines=True, stderr=subprocess.PIPE) - if res.returncode != 0: - print(res.stderr, file=sys.stderr) - res.check_returncode() def _check_get_function(name, expected_func_cls, expected_ker_cls, From 000992cc1fdf4b271cbd05d9f9da8b3663828b97 Mon Sep 17 00:00:00 2001 From: 1fanwang <1fannnw@gmail.com> Date: Fri, 11 Sep 2026 21:47:33 -0700 Subject: [PATCH 4/4] GH-51228: [Python] Reject invalid UDF registration registries Signed-off-by: 1fanwang <1fannnw@gmail.com> --- python/pyarrow/_compute.pyx | 2 ++ python/pyarrow/tests/test_compute.py | 19 +++++++++++++++++++ 2 files changed, 21 insertions(+) diff --git a/python/pyarrow/_compute.pyx b/python/pyarrow/_compute.pyx index 3390abaedb05..ed330d50ce64 100644 --- a/python/pyarrow/_compute.pyx +++ b/python/pyarrow/_compute.pyx @@ -3511,6 +3511,8 @@ def _register_user_defined_function(register_func, func, function_name, function if func_registry is None: c_func_registry = NULL + elif not isinstance(func_registry, FunctionRegistry): + raise TypeError("func_registry must be a FunctionRegistry") else: c_func_registry = (func_registry).registry diff --git a/python/pyarrow/tests/test_compute.py b/python/pyarrow/tests/test_compute.py index 36724efd7b61..ce93645e1ba5 100644 --- a/python/pyarrow/tests/test_compute.py +++ b/python/pyarrow/tests/test_compute.py @@ -257,6 +257,25 @@ def test_call_tabular_function_rejects_invalid_registry(): pc.call_tabular_function("", None, 1) +@pytest.mark.parametrize("register_function", [ + pc.register_scalar_function, + pc.register_vector_function, + pc.register_aggregate_function, + pc.register_tabular_function, +]) +def test_register_function_rejects_invalid_registry(register_function): + with pytest.raises(TypeError, + match="func_registry must be a FunctionRegistry"): + register_function( + func=lambda context: None, + function_name="invalid_registry", + function_doc={"summary": "", "description": ""}, + in_types={}, + out_type=pa.struct([]), + func_registry=1, + ) + + def _check_get_function(name, expected_func_cls, expected_ker_cls, min_num_kernels=1): func = pc.get_function(name)