mirror of
https://github.com/lancedb/lancedb.git
synced 2026-11-13 14:16:54 +00:00
fix(python): support custom blob thresholds without pylance (#4634)
This commit is contained in:
@@ -59,6 +59,124 @@ def _blob_v2_table(db: DBConnection, name: str):
|
||||
return table
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"threshold",
|
||||
[
|
||||
"inline_size_threshold",
|
||||
"dedicated_size_threshold",
|
||||
"pack_file_size_threshold",
|
||||
],
|
||||
)
|
||||
@pytest.mark.parametrize(
|
||||
"value,error",
|
||||
[
|
||||
(-1, ValueError),
|
||||
(True, TypeError),
|
||||
(False, TypeError),
|
||||
(1.5, TypeError),
|
||||
("8192", TypeError),
|
||||
(ctypes.c_size_t(-1).value + 1, OverflowError),
|
||||
],
|
||||
)
|
||||
def test_blob_thresholds_reject_invalid_values(threshold, value, error):
|
||||
# Also runs in CI with pylance actually uninstalled.
|
||||
with pytest.raises(error, match=f"{threshold} must"):
|
||||
lancedb.blob("blob", **{threshold: value})
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"threshold", ["dedicated_size_threshold", "pack_file_size_threshold"]
|
||||
)
|
||||
def test_blob_thresholds_reject_zero_for_positive_thresholds(threshold):
|
||||
with pytest.raises(ValueError, match=f"{threshold} must be positive"):
|
||||
lancedb.blob("blob", **{threshold: 0})
|
||||
|
||||
|
||||
def test_blob_thresholds_accept_integer_boundaries():
|
||||
maximum = ctypes.c_size_t(-1).value
|
||||
field = lancedb.blob(
|
||||
"blob",
|
||||
nullable=False,
|
||||
inline_size_threshold=0,
|
||||
dedicated_size_threshold=maximum,
|
||||
pack_file_size_threshold=maximum,
|
||||
)
|
||||
assert not field.nullable
|
||||
assert field.metadata == {
|
||||
b"lance-encoding:blob-inline-size-threshold": b"0",
|
||||
b"lance-encoding:blob-dedicated-size-threshold": str(maximum).encode(),
|
||||
b"lance-encoding:blob-pack-file-size-threshold": str(maximum).encode(),
|
||||
}
|
||||
|
||||
|
||||
def test_blob_thresholds_control_writes_after_reopen(tmp_path):
|
||||
# Keep this in test_table.py: CI runs it without pylance or pandas installed.
|
||||
field = lancedb.blob(
|
||||
"blob",
|
||||
inline_size_threshold=4,
|
||||
dedicated_size_threshold=12,
|
||||
pack_file_size_threshold=8,
|
||||
)
|
||||
metadata = {
|
||||
b"lance-encoding:blob-inline-size-threshold": b"4",
|
||||
b"lance-encoding:blob-dedicated-size-threshold": b"12",
|
||||
b"lance-encoding:blob-pack-file-size-threshold": b"8",
|
||||
}
|
||||
assert field.metadata == metadata
|
||||
schema = pa.schema([pa.field("id", pa.int64()), field])
|
||||
db = lancedb.connect(tmp_path)
|
||||
table = db.create_table("blobs", schema=schema)
|
||||
# Every payload fits in the default inline tier; custom thresholds must
|
||||
# instead send the larger values to packed and dedicated storage.
|
||||
rows = [
|
||||
{"id": 0, "blob": b"a" * 2},
|
||||
{"id": 1, "blob": b"b" * 8},
|
||||
{"id": 2, "blob": b"c" * 16},
|
||||
{"id": 3, "blob": b"d" * 8},
|
||||
]
|
||||
|
||||
def check_writes(table, expected):
|
||||
assert table.schema.field("blob").metadata.items() >= metadata.items()
|
||||
hits = table.search().with_row_id(True).limit(10).to_arrow()
|
||||
actual = {row["id"]: row for row in hits.to_pylist()}
|
||||
assert actual.keys() == {row["id"] for row in expected}
|
||||
for row in expected:
|
||||
size = len(row["blob"])
|
||||
descriptor = actual[row["id"]]["blob"]
|
||||
assert descriptor["size"] == size
|
||||
assert descriptor["kind"] == (2 if size > 12 else 1 if size > 4 else 0)
|
||||
row_ids = [actual[row["id"]]["_rowid"] for row in expected]
|
||||
assert table.fetch_blobs("blob", row_ids).to_pylist() == [
|
||||
row["blob"] for row in expected
|
||||
]
|
||||
# Two packed values reach the configured sidecar limit independently.
|
||||
packed = [
|
||||
actual[row["id"]]["blob"] for row in expected if len(row["blob"]) == 8
|
||||
]
|
||||
assert len({desc["blob_id"] for desc in packed}) == len(packed)
|
||||
|
||||
table.add(rows)
|
||||
check_writes(table, rows)
|
||||
table = lancedb.connect(tmp_path).open_table("blobs")
|
||||
check_writes(table, rows)
|
||||
updates = [
|
||||
{"id": 0, "blob": b"e" * 16},
|
||||
{"id": 1, "blob": b"f" * 2},
|
||||
{"id": 2, "blob": b"g" * 8},
|
||||
{"id": 3, "blob": b"h" * 8},
|
||||
{"id": 4, "blob": b"i" * 16},
|
||||
]
|
||||
result = (
|
||||
table.merge_insert("id")
|
||||
.when_matched_update_all()
|
||||
.when_not_matched_insert_all()
|
||||
.execute(updates)
|
||||
)
|
||||
assert result.num_updated_rows == 4
|
||||
assert result.num_inserted_rows == 1
|
||||
check_writes(lancedb.connect(tmp_path).open_table("blobs"), updates)
|
||||
|
||||
|
||||
async def _blob_v2_table_async(db: AsyncConnection, name: str):
|
||||
schema = pa.schema([pa.field("id", pa.int64()), lancedb.blob("blob")])
|
||||
table = await db.create_table(name, schema=schema)
|
||||
|
||||
Reference in New Issue
Block a user