Skip to content

Commit 5449b07

Browse files
authored
Merge pull request #2702 from mabel-dev/cythonize-LENGTH-function
Cythonize LENGTH function
2 parents 3195598 + 8eeeee3 commit 5449b07

4 files changed

Lines changed: 64 additions & 22 deletions

File tree

opteryx/__version__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
__build__ = 1394
1+
__build__ = 1396
22

33
# Licensed under the Apache License, Version 2.0 (the "License");
44
# you may not use this file except in compliance with the License.
Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
# cython: language_level=3
2+
# cython: nonecheck=False
3+
# cython: cdivision=True
4+
# cython: initializedcheck=False
5+
# cython: infer_types=True
6+
# cython: wraparound=False
7+
# cython: boundscheck=False
8+
9+
import numpy
10+
cimport numpy
11+
numpy.import_array()
12+
13+
import pyarrow
14+
15+
from libc.stdint cimport int32_t, uint32_t
16+
17+
cpdef numpy.ndarray[numpy.uint32_t, ndim=1] list_length(object array):
18+
19+
cdef Py_ssize_t n
20+
cdef numpy.ndarray[numpy.uint32_t, ndim=1] result
21+
cdef uint32_t[::1] result_view
22+
cdef object val
23+
cdef uint32_t i
24+
cdef numpy.ndarray[numpy.int32_t, ndim=1] offsets
25+
cdef int32_t[::1] offsets_view
26+
27+
# PyArrow fast path (uses offsets buffer)
28+
if isinstance(array, (pyarrow.Array, pyarrow.ChunkedArray, pyarrow.lib.StringArray)):
29+
if isinstance(array, pyarrow.ChunkedArray):
30+
array = array.combine_chunks()
31+
32+
n = len(array)
33+
34+
try:
35+
offsets_buffer = array.buffers()[1]
36+
offsets = numpy.frombuffer(offsets_buffer, dtype=numpy.int32, count=n + 1)
37+
offsets_view = offsets
38+
return (offsets[1:] - offsets[:-1]).astype(numpy.uint32)
39+
except Exception:
40+
pass # fallback if offsets unavailable
41+
42+
n = array.shape[0]
43+
result = numpy.empty(n, dtype=numpy.uint32)
44+
result_view = result
45+
46+
for i in range(n):
47+
val = array[i]
48+
if isinstance(val, (str, bytes, list, numpy.ndarray)):
49+
result_view[i] = len(val)
50+
else:
51+
result_view[i] = 0
52+
53+
return result

opteryx/functions/__init__.py

Lines changed: 2 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@
2222
import opteryx
2323
from opteryx.compiled.list_ops.list_contains_any import list_contains_any
2424
from opteryx.compiled.list_ops.list_encode_utf8 import list_encode_utf8 as to_blob
25+
from opteryx.compiled.list_ops.list_length import list_length
2526
from opteryx.exceptions import FunctionExecutionError
2627
from opteryx.exceptions import IncorrectTypeError
2728
from opteryx.functions import date_functions
@@ -260,19 +261,6 @@ def _inner(array, literal):
260261
return _inner
261262

262263

263-
def get_len(obj):
264-
"""len, but nullsafe"""
265-
if hasattr(obj, "__len__"):
266-
return len(obj)
267-
if hasattr(obj, "length"): # Some Arrow scalar types have .length property
268-
return obj.length
269-
if hasattr(obj, "nbytes"): # NumPy scalar types have .nbytes
270-
return obj.nbytes
271-
if hasattr(obj, "as_py") and isinstance(obj.as_py(), (bytes, str)): # PyArrow string scalar
272-
return len(obj.as_py())
273-
return None
274-
275-
276264
def _coalesce(*arrays):
277265
"""
278266
Element-wise coalesce function for multiple numpy arrays.
@@ -394,7 +382,7 @@ def sleep(x):
394382
"ASCII": (string_functions.to_ascii, "INTEGER", 1.0),
395383

396384
# STRINGS
397-
"LENGTH": (_iterate_single_parameter(get_len), "INTEGER", 1.0), # LENGTH(str) -> int
385+
"LENGTH": (list_length, "INTEGER", 1.0), # LENGTH(str) -> int
398386
"UPPER": (compute.utf8_upper, "VARCHAR", 1.0), # UPPER(str) -> str
399387
"LOWER": (compute.utf8_lower, "VARCHAR", 1.0), # LOWER(str) -> str
400388
"LEFT": (string_functions.string_slicer_left, "VARCHAR", 1.0),

opteryx/managers/expression/ops.py

Lines changed: 8 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
"""
66

77
import re
8+
from contextlib import suppress
89

910
import numpy
1011
import pyarrow
@@ -147,7 +148,7 @@ def _inner_filter_operations(arr, operator, value):
147148
"""
148149
if not operator.startswith(("AnyOp", "AllOp")) and len(value) == 1:
149150
value = value[0]
150-
if hasattr(value, "item"):
151+
with suppress(AttributeError):
151152
value = value.item()
152153
if isinstance(value, (tuple, list)):
153154
value = pyarrow.array(value)
@@ -165,24 +166,24 @@ def _inner_filter_operations(arr, operator, value):
165166
if operator == "GtEq":
166167
return compute.greater_equal(arr, value).to_numpy(False).astype(dtype=bool)
167168
if operator == "InList":
168-
if hasattr(value, "to_pylist"):
169+
with suppress(AttributeError):
169170
value = value.to_pylist()
170-
if hasattr(value, "to_numpy"):
171+
with suppress(AttributeError):
171172
value = value.to_numpy(zero_copy_only=False)
172173
values = set(value)
173-
if hasattr(arr, "to_numpy"):
174+
with suppress(AttributeError):
174175
arr = arr.to_numpy(zero_copy_only=False)
175176
if arr.dtype == numpy.int64:
176177
return list_ops.list_in_list.list_in_list_int64(memoryview(arr), values, len(arr))
177178
else:
178179
return list_ops.list_in_list.list_in_list(arr.astype(object), values)
179180
if operator == "NotInList":
180-
if hasattr(value, "to_pylist"):
181+
with suppress(AttributeError):
181182
value = value.to_pylist()
182-
if hasattr(value, "to_numpy"):
183+
with suppress(AttributeError):
183184
value = value.to_numpy(zero_copy_only=False)
184185
values = set(value)
185-
if hasattr(arr, "to_numpy"):
186+
with suppress(AttributeError):
186187
arr = arr.to_numpy(zero_copy_only=False)
187188
if arr.dtype == numpy.int64:
188189
matches = list_ops.list_in_list.list_in_list_int64(memoryview(arr), values, len(arr))

0 commit comments

Comments
 (0)