Skip to content

Commit dc28500

Browse files
author
Gordon Murray
committed
fix: use native LanceDB pagination instead of full table load
Use count_rows() + take_offsets() to read only the requested page from disk, avoiding the full table load. Falls back to Arrow slice() on older Lance versions that lack these methods. Replaces the to_pylist() approach that converted the entire dataset to Python dicts before paginating. Tested: lancedb 0.24.3 (native path), 0.3.4 (fallback path). Untested: 0.16.0, 0.5, 0.3.1 — fallback should handle these. Closes #13
1 parent 0546cf9 commit dc28500

1 file changed

Lines changed: 26 additions & 51 deletions

File tree

backend/app.py

Lines changed: 26 additions & 51 deletions
Original file line numberDiff line numberDiff line change
@@ -224,7 +224,7 @@ async def get_dataset_columns(dataset_name: str):
224224
@app.get("/datasets/{dataset_name}/rows")
225225
async def get_dataset_rows(
226226
dataset_name: str,
227-
limit: int = Query(default=50, le=MAX_LIMIT),
227+
limit: int = Query(default=50, ge=1, le=MAX_LIMIT),
228228
offset: int = Query(default=0, ge=0),
229229
columns: Optional[str] = Query(default=None)
230230
):
@@ -294,60 +294,35 @@ async def get_dataset_rows(
294294
logger.info(f"Returned schema info for corrupted {dataset_name} dataset")
295295

296296
else:
297-
# For other datasets, try normal reading
298-
logger.info(f"Attempting to read {dataset_name} using to_arrow().to_pylist() approach")
299-
300-
# This is the approach that works in the search API
301-
data_list = table.to_arrow().to_pylist()
302-
total_count = len(data_list)
303-
304-
# Apply pagination at the list level
305-
start_idx = offset
306-
end_idx = min(offset + limit, total_count)
307-
paginated_data = data_list[start_idx:end_idx]
308-
309-
# Convert back to Arrow table for consistent processing
310-
if paginated_data:
311-
# Get the schema from the original table
312-
schema = table.schema
297+
try:
298+
# Native pagination: read only the requested rows from disk
299+
total_count = table.count_rows()
300+
end = min(offset + limit, total_count)
301+
if offset >= total_count:
302+
result_table = pa.table({field.name: pa.array([], type=field.type) for field in table.schema})
303+
else:
304+
offsets = list(range(offset, end))
305+
builder = table.take_offsets(offsets)
306+
if column_list:
307+
available_columns = [col for col in column_list if col in [field.name for field in table.schema]]
308+
if available_columns:
309+
builder = builder.select(available_columns)
310+
result_table = builder.to_arrow()
311+
312+
logger.info(f"Read {result_table.num_rows} rows (offset={offset}, limit={limit}) from {dataset_name} ({total_count} total)")
313+
314+
except (AttributeError, TypeError):
315+
# Fallback for older Lance versions without take_offsets/count_rows
316+
logger.info(f"Native pagination unavailable, using Arrow slice for {dataset_name}")
317+
arrow_table = table.to_arrow()
318+
total_count = arrow_table.num_rows
313319

314-
# Apply column selection if specified
315-
if column_list:
316-
# Filter the schema and data
317-
available_columns = [col for col in column_list if col in [field.name for field in schema]]
318-
if available_columns:
319-
# Create filtered data
320-
filtered_data = []
321-
for row in paginated_data:
322-
filtered_row = {col: row.get(col) for col in available_columns}
323-
filtered_data.append(filtered_row)
324-
paginated_data = filtered_data
325-
326-
# Create filtered schema
327-
filtered_fields = [field for field in schema if field.name in available_columns]
328-
schema = pa.schema(filtered_fields)
329-
330-
# Convert the paginated data back to Arrow format
331-
arrays = []
332-
for field in schema:
333-
column_data = [row.get(field.name) for row in paginated_data]
334-
arrays.append(pa.array(column_data, type=field.type))
335-
336-
result_table = pa.Table.from_arrays(arrays, schema=schema)
337-
else:
338-
# Empty result - create empty table with correct schema
339-
schema = table.schema
340320
if column_list:
341-
available_columns = [col for col in column_list if col in [field.name for field in schema]]
321+
available_columns = [col for col in column_list if col in arrow_table.column_names]
342322
if available_columns:
343-
filtered_fields = [field for field in schema if field.name in available_columns]
344-
schema = pa.schema(filtered_fields)
345-
346-
# Create empty arrays for each field
347-
arrays = [pa.array([], type=field.type) for field in schema]
348-
result_table = pa.Table.from_arrays(arrays, schema=schema)
323+
arrow_table = arrow_table.select(available_columns)
349324

350-
logger.info(f"Successfully read {len(paginated_data)} rows from {dataset_name}")
325+
result_table = arrow_table.slice(offset, limit)
351326

352327
except Exception as general_error:
353328
logger.error(f"Reading failed for {dataset_name}: {general_error}")

0 commit comments

Comments
 (0)