Skip to content

Commit 7b1cc20

Browse files
authored
Merge pull request #2901 from mabel-dev/#2900
New disk listing code
2 parents bcda9e6 + 45894e1 commit 7b1cc20

7 files changed

Lines changed: 483 additions & 18 deletions

File tree

opteryx/__version__.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,9 @@
11
# THIS FILE IS AUTOMATICALLY UPDATED DURING THE BUILD PROCESS
22
# DO NOT EDIT THIS FILE DIRECTLY
33

4-
__build__ = 1739
4+
__build__ = 1740
55
__author__ = "@joocer"
6-
__version__ = "0.26.2-beta.1739"
6+
__version__ = "0.26.2-beta.1740"
77

88
# Store the version here so:
99
# 1) we don't load dependencies by storing it in __init__.py

opteryx/compiled/io/disk_reader.pyx

Lines changed: 118 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -10,25 +10,39 @@
1010
Ultra-fast disk reader module
1111
"""
1212

13+
import errno
14+
1315
from cpython.buffer cimport PyBuffer_FillInfo
14-
from libc.stdlib cimport free
16+
from cpython.mem cimport PyMem_Free
17+
from cpython.mem cimport PyMem_Malloc
18+
from cpython.unicode cimport PyUnicode_FromString
19+
from libc.stddef cimport size_t
1520

1621
cdef extern from "disk_io.h":
1722
int read_all_pread(const char* path, unsigned char* dst, size_t* out_len,
1823
bint sequential, bint willneed, bint drop_after)
1924
int read_all_mmap(const char* path, unsigned char** dst, size_t* out_len)
2025
int unmap_memory_c(unsigned char* addr, size_t size)
2126

27+
cdef extern from "directories.h":
28+
ctypedef struct file_info_t:
29+
char* name
30+
int is_directory
31+
int is_regular_file
32+
long long size
33+
long long mtime
34+
35+
int list_directory_c "list_directory"(const char* path, file_info_t** files, size_t* count)
36+
void free_file_list(file_info_t* files, size_t count)
37+
int list_matching_files_c "list_matching_files"(const char* base_path, const char** extensions,
38+
size_t ext_count, char*** files, size_t* count) nogil
39+
void free_file_names(char** files, size_t count)
40+
2241
cdef class MappedMemory:
2342
cdef unsigned char* data
2443
cdef size_t size
2544
cdef bint owned
2645

27-
def __dealloc__(self):
28-
if self.owned and self.data != NULL:
29-
# Free the allocated memory (for non-mmap case)
30-
free(self.data)
31-
3246
def __getbuffer__(self, Py_buffer* buffer, int flags):
3347
PyBuffer_FillInfo(buffer, self, self.data, self.size, 1, flags)
3448

@@ -64,6 +78,104 @@ def read_file(str path, bint sequential=True, bint willneed=True, bint drop_afte
6478
return memoryview(buf)[:out_len]
6579

6680

81+
def list_directory(str path):
82+
"""Return directory entries using the native file system scanner."""
83+
84+
path_b = path.encode("utf-8")
85+
cdef const char* c_path = path_b
86+
cdef file_info_t* files = NULL
87+
cdef size_t count = 0
88+
89+
cdef int rc = list_directory_c(c_path, &files, &count)
90+
if rc != 0:
91+
err = -rc
92+
if err == errno.ENOENT:
93+
raise FileNotFoundError(path)
94+
raise OSError(err, f"Failed to list directory: {path}")
95+
96+
entries = []
97+
cdef file_info_t entry
98+
cdef char* name_ptr
99+
cdef size_t idx
100+
try:
101+
for idx in range(count):
102+
entry = files[idx]
103+
name_ptr = entry.name
104+
if name_ptr == NULL:
105+
continue
106+
107+
py_name = PyUnicode_FromString(name_ptr)
108+
entries.append(
109+
(
110+
py_name,
111+
bool(entry.is_directory),
112+
bool(entry.is_regular_file),
113+
entry.size,
114+
entry.mtime,
115+
)
116+
)
117+
finally:
118+
if files != NULL:
119+
free_file_list(files, count)
120+
121+
return entries
122+
123+
124+
def list_files(str path, extensions):
125+
"""Return a list of files under ``path`` matching provided extensions."""
126+
127+
if extensions is None:
128+
raise ValueError("extensions must be provided")
129+
130+
ext_seq = tuple(extensions)
131+
ext_count = len(ext_seq)
132+
133+
cdef size_t c_ext_count = ext_count
134+
cdef const char** ext_array = NULL
135+
cdef char** file_array = NULL
136+
cdef size_t file_count = 0
137+
138+
ext_bytes = [ext.encode("utf-8") if isinstance(ext, str) else ext for ext in ext_seq]
139+
140+
if c_ext_count > 0:
141+
ext_array = <const char**>PyMem_Malloc(c_ext_count * sizeof(const char*))
142+
if ext_array == NULL:
143+
raise MemoryError("Unable to allocate extension array")
144+
for idx in range(c_ext_count):
145+
ext_array[idx] = <const char*>ext_bytes[idx]
146+
147+
path_b = path.encode("utf-8")
148+
cdef const char* c_path = path_b
149+
150+
cdef int rc
151+
with nogil:
152+
rc = list_matching_files_c(c_path, ext_array, c_ext_count, &file_array, &file_count)
153+
154+
if ext_array != NULL:
155+
PyMem_Free(ext_array)
156+
157+
if rc != 0:
158+
if file_array != NULL:
159+
free_file_names(file_array, file_count)
160+
err = -rc
161+
if err == errno.ENOENT:
162+
raise FileNotFoundError(path)
163+
raise OSError(err, f"Failed to list files: {path}")
164+
165+
results = []
166+
try:
167+
for idx in range(file_count):
168+
py_path = PyUnicode_FromString(file_array[idx])
169+
if py_path is None:
170+
raise MemoryError("Unable to decode file path")
171+
results.append(py_path)
172+
finally:
173+
if file_array != NULL:
174+
free_file_names(file_array, file_count)
175+
176+
return results
177+
178+
67179
def read_file_mmap(str path):
68180
"""
69181
Read file using memory mapping - returns an object that provides memoryview interface

opteryx/connectors/disk_connector.py

Lines changed: 18 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
from concurrent.futures import FIRST_COMPLETED
1616
from concurrent.futures import ThreadPoolExecutor
1717
from concurrent.futures import wait
18+
from functools import lru_cache
1819
from typing import Dict
1920
from typing import List
2021

@@ -38,6 +39,11 @@
3839
OS_SEP = os.sep
3940

4041

42+
@lru_cache(maxsize=1)
43+
def _get_disk_reader_module():
44+
return importlib.import_module("opteryx.compiled.io.disk_reader")
45+
46+
4147
class DiskConnector(BaseConnector, Partitionable, PredicatePushable, LimitPushable, Statistics):
4248
"""
4349
Connector for reading datasets from files on local storage.
@@ -121,8 +127,8 @@ def read_blob(
121127
OSError:
122128
If an I/O error occurs while reading the file.
123129
"""
124-
disk_reader = importlib.import_module("opteryx.compiled.io.disk_reader")
125-
read_file_mmap = getattr(disk_reader, "read_file_mmap")
130+
disk_reader = _get_disk_reader_module()
131+
read_file_mmap = disk_reader.read_file_mmap
126132

127133
# from opteryx.compiled.io.disk_reader import unmap_memory
128134
# Read using mmap for maximum speed
@@ -174,12 +180,16 @@ def get_list_of_blob_names(self, *, prefix: str) -> List[str]:
174180
if prefix in self.blob_list:
175181
return self.blob_list[prefix]
176182

177-
blobs = sorted(
178-
os.path.join(root, file)
179-
for root, _, files in os.walk(prefix + OS_SEP)
180-
for file in files
181-
if file.endswith(TUPLE_OF_VALID_EXTENSIONS)
182-
)
183+
disk_reader = _get_disk_reader_module()
184+
list_files = getattr(disk_reader, "list_files", None)
185+
if list_files is None:
186+
raise RuntimeError("Compiled disk reader missing list_files helper")
187+
188+
target = os.path.normpath(prefix)
189+
try:
190+
blobs = sorted(list_files(target, TUPLE_OF_VALID_EXTENSIONS))
191+
except FileNotFoundError:
192+
blobs = []
183193

184194
self.blob_list[prefix] = blobs
185195
return blobs

pyproject.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
[project]
22
name = "opteryx"
3-
version = "0.26.2-beta.1739"
3+
version = "0.26.2-beta.1740"
44
description = "Query your data, where it lives"
55
requires-python = '>=3.11'
66
readme = {file = "README.md", content-type = "text/markdown"}

setup.py

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -250,10 +250,15 @@ def rust_build(setup_kwargs: Dict[str, Any]) -> None:
250250
),
251251
Extension(
252252
name="opteryx.compiled.io.disk_reader",
253-
sources=["opteryx/compiled/io/disk_reader.pyx", "src/cpp/disk_io.cpp"],
253+
sources=[
254+
"opteryx/compiled/io/disk_reader.pyx",
255+
"src/cpp/disk_io.cpp",
256+
"src/cpp/directories.cpp",
257+
],
254258
include_dirs=include_dirs + ["src/cpp"],
255259
language="c++",
256260
extra_compile_args=CPP_COMPILE_FLAGS,
261+
depends=["src/cpp/directories.h"],
257262
),
258263
Extension(
259264
name="opteryx.compiled.table_ops.distinct",

0 commit comments

Comments
 (0)