Files
alibaba--zvec/python/tests/detail/test_collection_dml.py
T
2026-07-13 12:47:42 +08:00

1112 lines
36 KiB
Python

import logging
import pytest
from zvec import (
CollectionOption,
InvertIndexParam,
HnswIndexParam,
FieldSchema,
VectorSchema,
CollectionSchema,
Collection,
Doc,
Query,
StatusCode,
)
from distance_helper import *
from fixture_helper import *
from doc_helper import *
Maximum = 1024
DOCID_VALID_LIST = [
"1valid_Id",
"123.45",
"123abc",
"-!@#$%+=.123abc_+",
"abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ123456789012",
]
DOCID_INVALID_LIST = [
None,
"",
"()qsd123",
" ",
"/&AS12",
"abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890121",
]
FIELD_VALUE_VALID_LIST = [
(
"bool_field",
[
None,
True,
False,
],
),
(
"float_field",
[
None,
0.0,
-1.0,
1.0,
3.4028235e38,
-3.4028235e38,
1.17549435e-38,
-1.17549435e-38,
float("inf"),
float("-inf"),
],
),
(
"double_field",
[
None,
0.0,
-1.0,
1.0,
1.7976931348623157e308,
-1.7976931348623157e308,
2.2250738585072014e-308,
-2.2250738585072014e-308,
float("inf"),
float("-inf"),
],
),
(
"int32_field",
[
None,
0,
1,
-1,
2147483647,
-2147483648,
],
),
(
"int64_field",
[
None,
0,
1,
-1,
9223372036854775807,
-9223372036854775808,
],
),
(
"uint32_field",
[
None,
0,
1,
4294967295,
],
),
(
"uint64_field",
[
None,
0,
1,
18446744073709551615,
],
),
(
"string_field",
[
None,
"",
"a",
"test_name",
"这是一个中文名称测试",
"a" * 1000,
],
),
(
"array_bool_field",
[
None,
[],
[True],
[False, True],
[True, False, True, False] * 10,
],
),
(
"array_float_field",
[
None,
[],
[0.0],
[1.0, 2.0, 3.0],
[3.4028235e38, -3.4028235e38],
],
),
(
"array_double_field",
[
None,
[],
[0.0],
[1.0, 2.0, 3.0],
[1.7976931348623157e308, -1.7976931348623157e308],
],
),
(
"array_int32_field",
[
None,
[],
[0],
[1, 2, 3],
[2147483647, -2147483648],
],
),
(
"array_int64_field",
[
None,
[],
[0],
[1, 2, 3],
[9223372036854775807, -9223372036854775808],
],
),
(
"array_uint32_field",
[
None,
[],
[0],
[1, 2, 3],
[4294967295],
],
),
(
"array_uint64_field",
[
None,
[],
[0],
[1, 2, 3],
[18446744073709551615],
],
),
(
"array_string_field",
[
None,
[],
[""],
["a", "b", "c"],
["test_string", "测试字符串"],
["a" * 100] * 5,
],
),
]
FIELD_VALUE_INVALID_LIST = [
(
"bool_field",
["True", "False", "", "测试"],
),
("float_field", ["invalid", [1.0], {"value": 1.0}, "测试"]),
("double_field", ["invalid", [1.0], {"value": 1.0}, "测试"]),
(
"int32_field",
["invalid", [1], {"value": 1}, 2147483648, -2147483649, "测试"],
),
(
"int64_field",
[
"invalid",
[1],
{"value": 1},
9223372036854775808,
-9223372036854775809,
"测试",
],
),
(
"uint32_field",
["invalid", [1], {"value": 1}, 4294967296, -1, "测试"],
),
(
"uint64_field",
["invalid", [1], {"value": 1}, 18446744073709551616, -1, "测试"],
),
(
"string_field",
[
123,
12.34,
True,
["array"],
{"key": "value"},
],
),
(
"array_bool_field",
[True, False, [True, "invalid"], {"key": True}, "测试"],
),
(
"array_float_field",
[[1.0, "invalid"], [1.0, None], "invalid", [1.0, [2.0]], 1.0, "测试"],
),
(
"array_double_field",
[[1.0, "invalid"], [1.0, None], "invalid", [1.0, [2.0]], 1.0, "测试"],
),
(
"array_int32_field",
[[1, "invalid"], [1, None], "invalid", [1, [2]], 1, "测试"],
),
(
"array_int64_field",
[[1, "invalid"], [1, None], "invalid", [1, [2]], 1, "测试"],
),
(
"array_uint32_field",
[[1, "invalid"], [1, None], [1, -1], "invalid", [1, [2]], 1, "测试"],
),
(
"array_uint64_field",
[[1, "invalid"], [1, None], [1, -1], "invalid", [1, [2]], 1, "测试"],
),
(
"array_string_field",
[["valid", 123], ["valid", None], "invalid", [["nested"]], 123, "测试"],
),
]
VECTOR_VALUE_VALID_LIST = [
(
"vector_fp32_field",
[
[0.0] * 128,
[1.0] * 128,
[-1.0] * 128,
[float("inf")] * 128,
[float("-inf")] * 128,
[i / 128.0 for i in range(128)],
[-i / 128.0 for i in range(128)],
],
),
(
"vector_fp16_field",
[
[0.0] * 128,
[1.0] * 128,
[-1.0] * 128,
[float("inf")] * 128,
[float("-inf")] * 128,
[i / 128.0 for i in range(128)],
[-i / 128.0 for i in range(128)],
],
),
("vector_int8_field", [[100] * 128, [0] * 128, [-100] * 128]),
(
"sparse_vector_fp32_field",
[
{0: 1.0},
{0: 0.0, 1: 1.0, 2: -1.0},
{0: float("inf"), 1: float("-inf")},
{i: float(i) for i in range(10)},
{128: 1.0, 256: -1.0, 512: 0.5},
],
),
(
"sparse_vector_fp16_field",
[
{0: 1.0},
{0: 0.0, 1: 1.0, 2: -1.0},
{0: float("inf"), 1: float("-inf")},
{i: float(i) for i in range(10)},
{128: 1.0, 256: -1.0, 512: 0.5},
],
),
]
VECTOR_VALUE_INVALID_LIST = [
(
"vector_fp32_field",
[
None,
[],
[0.0] * 127,
[0.0] * 129,
[0.0] * 1000,
["invalid"],
[0, 1, 2],
[None] * 128,
],
),
(
"vector_fp16_field",
[
None,
[],
[0.0] * 127,
[0.0] * 129,
[0.0] * 1000,
["invalid"],
[0, 1, 2],
[None] * 128,
],
),
(
"vector_int8_field",
[
None,
[],
[1] * 127,
[10] * 129,
[0] * 1000,
["invalid"],
[0, 1, 2],
[None] * 128,
],
),
(
"sparse_vector_fp32_field",
[
None,
"invalid",
{None: 1.0},
{"0": 1.0},
{0: "invalid"},
{0: None},
{-1: 1.0},
],
),
(
"sparse_vector_fp16_field",
[
None,
"invalid",
{None: 1.0},
{"0": 1.0},
{0: "invalid"},
{0: None},
{-1: 1.0},
],
),
]
UPDATE_PARTIAL_VALUE = [
(
"partial_fields",
{"string_field": "partially_updated_test", "float_field": 95.5},
{},
),
("dense_vector_only", {}, {"vector_fp32_field": [0.3] * 128}),
("dense_vector_only", {}, {"vector_fp16_field": [0.6] * 128}),
("dense_vector_only", {}, {"vector_int8_field": [3] * 128}),
("sparse_vector_only", {}, {"sparse_vector_fp32_field": {1: 2.0, 2: 3.0, 4: 4.0}}),
(
"sparse_vector_only",
{},
{"sparse_vector_fp16_field": {10: 2.1, 20: 3.1, 40: 4.1}},
),
(
"fields_and_vectors",
{"string_field": "fully_updated_test", "bool_field": False},
{
"vector_fp32_field": [0.4] * 128,
"sparse_vector_fp32_field": {1: 3.0, 3: 5.0},
},
),
]
# ==================== helper ====================
def singledoc_and_check(
collection: Collection, insert_doc, operator="insert", is_delete=1
):
if operator == "insert":
result = collection.insert(insert_doc)
elif operator == "upsert":
result = collection.upsert(insert_doc)
elif operator == "update":
result = collection.update(insert_doc)
else:
logging.error("operator value is error!")
assert bool(result)
assert result.ok()
stats = collection.stats
assert stats is not None
assert stats.doc_count == 1
fetched_docs = collection.fetch([insert_doc.id])
assert len(fetched_docs) == 1
assert insert_doc.id in fetched_docs
fetched_doc = fetched_docs[insert_doc.id]
assert is_doc_equal(fetched_doc, insert_doc, collection.schema)
assert hasattr(fetched_doc, "score"), "Document should have a score attribute"
assert fetched_doc.score == 0.0, (
"Fetch operation should return default score of 0.0"
)
for k, v in DEFAULT_VECTOR_FIELD_NAME.items():
if v != {}:
query_result = collection.query(
Query(field_name=v, vector=insert_doc.vectors[v]),
topk=10,
)
assert len(query_result) > 0, (
f"Expected at least 1 query result, but got {len(query_result)}"
)
found_doc = None
for doc in query_result:
if doc.id == insert_doc.id:
found_doc = doc
break
assert found_doc is not None, (
f"Inserted document {insert_doc.id} not found in query results"
)
assert is_doc_equal(found_doc, insert_doc, collection.schema, True, False)
if is_delete == 1:
collection.delete(insert_doc.id)
assert collection.stats.doc_count == 0, "Document should be deleted"
def updatedoc_partial_check(
collection, update_doc_partial, update_doc_full, operator="update", is_delete=1
):
if operator == "upsert":
result = collection.upsert(update_doc_partial)
elif operator == "update":
result = collection.update(update_doc_partial)
else:
logging.error("operator value is error!")
assert bool(result)
assert result.ok()
stats = collection.stats
assert stats is not None
assert stats.doc_count == 1
fetched_docs = collection.fetch([update_doc_partial.id])
assert len(fetched_docs) == 1, (
f"fetched_docs={fetched_docs},Expected 1 fetched document, but got {len(fetched_docs)}"
)
assert update_doc_partial.id in fetched_docs, (
f"Expected document ID {update_doc_partial.id} in fetched documents"
)
fetched_doc = fetched_docs[update_doc_partial.id]
assert is_doc_equal(fetched_doc, update_doc_full, collection.schema)
assert hasattr(fetched_doc, "score"), "Document should have a score attribute"
assert fetched_doc.score == 0.0, (
"Fetch operation should return default score of 0.0"
)
for k, v in DEFAULT_VECTOR_FIELD_NAME.items():
if v != {}:
query_result = collection.query(
Query(field_name=v, vector=update_doc_full.vectors[v]),
topk=10,
)
assert len(query_result) > 0, (
f"Expected at least 1 query result, but got {len(query_result)}"
)
found_doc = None
for doc in query_result:
if doc.id == update_doc_partial.id:
found_doc = doc
break
assert found_doc is not None, (
f"Inserted document {update_doc_partial.id} not found in query results"
)
assert is_doc_equal(
found_doc, update_doc_full, collection.schema, True, False
)
if is_delete == 1:
collection.delete(update_doc_partial.id)
assert collection.stats.doc_count == 0, "Document should be deleted"
def batchdoc_and_check(collection, multiple_docs, doc_num, operator="insert"):
if operator == "insert":
result = collection.insert(multiple_docs)
elif operator == "upsert":
result = collection.upsert(multiple_docs)
elif operator == "update":
result = collection.update(multiple_docs)
else:
logging.error("operator value is error!")
assert len(result) == len(multiple_docs)
for item in result:
assert item.ok(), (
f"result={result},Insert operation failed with code {item.code()}"
)
stats = collection.stats
assert stats is not None, "Collection stats should not be None"
assert stats.doc_count == len(multiple_docs), (
f"Document count should be {len(multiple_docs)} after insert, but got {stats.doc_count}"
)
doc_ids = [doc.id for doc in multiple_docs]
fetched_docs = collection.fetch(doc_ids)
assert len(fetched_docs) == len(multiple_docs), (
f"fetched_docs={fetched_docs},Expected {len(multiple_docs)} fetched documents, but got {len(fetched_docs)}"
)
for original_doc in multiple_docs:
assert original_doc.id in fetched_docs, (
f"Expected document ID {original_doc.id} in fetched documents"
)
fetched_doc = fetched_docs[original_doc.id]
assert is_doc_equal(fetched_doc, original_doc, collection.schema)
assert hasattr(fetched_doc, "score"), "Document should have a score attribute"
assert fetched_doc.score == 0.0, (
"Fetch operation should return default score of 0.0"
)
first_doc = multiple_docs[doc_num - 1]
for k, v in DEFAULT_VECTOR_FIELD_NAME.items():
query_result = collection.query(
Query(field_name=v, vector=first_doc.vectors[v]),
topk=1024,
)
assert len(query_result) > 0, (
f"Expected at least 1 query result, but got {len(query_result)}"
)
found_doc = None
for doc in query_result:
if doc.id == first_doc.id:
found_doc = doc
break
assert found_doc is not None, (
f"Inserted document {first_doc.id} not found in query results"
)
assert is_doc_equal(found_doc, first_doc, collection.schema, True, False)
# ==================== Tests ====================
# ----------------------------
# Collection Insert Test Case
# ----------------------------
class TestCollectionInsert:
def test_insert(self, full_collection: Collection):
single_doc = generate_doc(1, full_collection.schema)
singledoc_and_check(full_collection, single_doc)
@pytest.mark.parametrize("doc_num", [1, 5, Maximum])
def test_insert_batch(self, full_collection: Collection, doc_num):
multiple_docs = [
generate_doc(i, full_collection.schema) for i in range(doc_num)
]
batchdoc_and_check(full_collection, multiple_docs, doc_num)
def test_insert_duplicate(self, full_collection: Collection):
insert_doc = generate_doc(1, full_collection.schema)
result = full_collection.insert(insert_doc)
assert result.code().value == 0
assert result.ok()
# Verify documents were inserted
stats = full_collection.stats
assert stats is not None
assert stats.doc_count == 1
insert_doc_duplicate = full_collection.insert(insert_doc)
assert bool(insert_doc_duplicate)
assert insert_doc_duplicate.code() == StatusCode.ALREADY_EXISTS, (
f"Second insert operation should fail with ALREADY_EXISTS, but got code {insert_doc_duplicate.code()}"
)
stats = full_collection.stats
assert stats is not None, "Collection stats should not be None"
assert stats.doc_count == 1, (
f"Document count should still be 1 after failed insert, but got {stats.doc_count}"
)
@pytest.mark.parametrize("doc_id", DOCID_VALID_LIST)
def test_insert_docid_valid(self, full_collection: Collection, doc_id):
insert_doc = generate_doc_random(doc_id, full_collection.schema)
singledoc_and_check(full_collection, insert_doc)
@pytest.mark.parametrize("doc_id", DOCID_INVALID_LIST)
def test_insert_docid_invalid(self, full_collection: Collection, doc_id):
insert_doc = generate_doc_random(doc_id, full_collection.schema)
with pytest.raises(Exception) as exc_info:
full_collection.insert(insert_doc)
assert exc_info.value is not None
stats = full_collection.stats
assert stats is not None
assert stats.doc_count == 0
@pytest.mark.parametrize("field_name, field_values", FIELD_VALUE_VALID_LIST)
@pytest.mark.parametrize(
"full_schema_new",
[(True, True, HnswIndexParam()), (False, True, HnswIndexParam())],
indirect=True,
)
def test_insert_fields_valid(
self, full_collection_new: Collection, field_name: str, field_values, request
):
for i, field_value in enumerate(field_values):
doc_id = str(field_value) if field_name == "id" else str(i)
doc_fields, doc_vectors = generate_vectordict_random(
full_collection_new.schema
)
full_schema_params = request.getfixturevalue("full_schema_new")
target_field = None
for field in full_schema_params.fields:
if field.name == field_name:
target_field = field
break
doc_fields[field_name] = field_value
insert_doc = Doc(id=doc_id, fields=doc_fields, vectors=doc_vectors)
if target_field and not target_field.nullable and field_value is None:
with pytest.raises(Exception) as exc_info:
full_collection_new.insert(insert_doc)
assert exc_info.value is not None
else:
singledoc_and_check(full_collection_new, insert_doc)
@pytest.mark.parametrize("field_name, field_values", FIELD_VALUE_INVALID_LIST)
def test_insert_fields_invalid(
self, full_collection: Collection, field_name: str, field_values
):
for i, field_value in enumerate(field_values):
doc_id = str(field_value) if field_name == "id" else str(i)
doc_fields, doc_vectors = generate_vectordict_random(full_collection.schema)
doc_fields[field_name] = field_value
insert_doc = Doc(id=doc_id, fields=doc_fields, vectors=doc_vectors)
with pytest.raises(Exception) as exc_info:
full_collection.insert(insert_doc)
assert exc_info.value is not None
stats = full_collection.stats
assert stats is not None
assert stats.doc_count == 0
@pytest.mark.parametrize("vector_field, vector_values", VECTOR_VALUE_VALID_LIST)
def test_insert_vector_valid(
self, full_collection: Collection, vector_field: str, vector_values
):
for i, vector_value in enumerate(vector_values):
doc_fields, doc_vectors = generate_vectordict_random(full_collection.schema)
doc_vectors[vector_field] = vector_value
insert_doc = Doc(id=str(i), fields=doc_fields, vectors=doc_vectors)
singledoc_and_check(full_collection, insert_doc)
@pytest.mark.parametrize("vector_field, vector_values", VECTOR_VALUE_INVALID_LIST)
def test_insert_vector_invalid(
self, full_collection: Collection, vector_field: str, vector_values
):
for i, vector_value in enumerate(vector_values):
doc_fields, doc_vectors = generate_vectordict_random(full_collection.schema)
doc_vectors[vector_field] = vector_value
insert_doc = Doc(id=str(i), fields=doc_fields, vectors=doc_vectors)
with pytest.raises(Exception) as exc_info:
full_collection.insert(insert_doc)
assert exc_info.value is not None
stats = full_collection.stats
assert stats is not None
assert stats.doc_count == 0
class TestCollectionUpdate:
def test_update(self, full_collection: Collection):
insert_doc = generate_doc(1, full_collection.schema)
singledoc_and_check(full_collection, insert_doc, is_delete=0)
updated_doc = generate_update_doc(1, full_collection.schema)
singledoc_and_check(full_collection, updated_doc, operator="update")
@pytest.mark.parametrize("doc_num", [1, 5, Maximum])
def test_update_batch(self, full_collection: Collection, doc_num):
multiple_docs = [
generate_doc(i, full_collection.schema) for i in range(doc_num)
]
batchdoc_and_check(full_collection, multiple_docs, doc_num)
multiple_update_docs = [
generate_update_doc(i, full_collection.schema) for i in range(doc_num)
]
batchdoc_and_check(
full_collection, multiple_update_docs, doc_num, operator="update"
)
def test_empty_collection_update(self, full_collection: Collection):
updated_doc = generate_update_doc(1, full_collection.schema)
result = full_collection.update(updated_doc)
assert bool(result), f"Expected 1 result, but got {len(result)}"
assert result.code() == StatusCode.NOT_FOUND, (
f"Update operation should fail with NOT_FOUND, but got code {result.code()}"
)
fetched_docs = full_collection.fetch([updated_doc.id])
assert len(fetched_docs) == 0
stats = full_collection.stats
assert stats is not None, "Collection stats should not be None"
assert stats.doc_count == 0, (
f"Document count should be 0, but got {stats.doc_count}"
)
@pytest.mark.parametrize("doc_num", [1, 5, Maximum])
def test_empty_collection_update_batch(self, full_collection: Collection, doc_num):
multiple_update_docs = [
generate_update_doc(i, full_collection.schema) for i in range(doc_num)
]
result = full_collection.update(multiple_update_docs)
assert len(result) == len(multiple_update_docs), (
f"Expected {len(multiple_update_docs)} results, but got {len(result)}"
)
for item in result:
assert item.code() == StatusCode.NOT_FOUND, (
f"Update operation should fail with NOT_FOUND, but got code {item.code()}"
)
stats = full_collection.stats
assert stats is not None, "Collection stats should not be None"
assert stats.doc_count == 0, (
f"Document count should be 0, but got {stats.doc_count}"
)
doc_ids = [doc.id for doc in multiple_update_docs]
fetched_docs = full_collection.fetch(doc_ids)
assert len(fetched_docs) == 0
@pytest.mark.parametrize("field_name, field_values", FIELD_VALUE_VALID_LIST)
@pytest.mark.parametrize(
"full_schema_new",
[(True, True, HnswIndexParam()), (False, True, HnswIndexParam())],
indirect=True,
)
def test_update_fields_valid(
self, full_collection_new: Collection, field_name: str, field_values, request
):
for i, field_value in enumerate(field_values):
insert_doc = generate_doc(i, full_collection_new.schema)
singledoc_and_check(full_collection_new, insert_doc, is_delete=0)
update_doc_fields, update_doc_vectors = generate_vectordict_random(
full_collection_new.schema
)
full_schema_params = request.getfixturevalue("full_schema_new")
target_field = None
for field in full_schema_params.fields:
if field.name == field_name:
target_field = field
break
update_doc_fields[field_name] = field_value
update_doc = Doc(
id=str(i), fields=update_doc_fields, vectors=update_doc_vectors
)
if target_field and not target_field.nullable and field_value is None:
with pytest.raises(Exception) as exc_info:
update_doc_fields[field_name] = field_value
full_collection_new.update(update_doc)
assert exc_info.value is not None
full_collection_new.delete(insert_doc.id)
else:
singledoc_and_check(
full_collection_new, update_doc, operator="update", is_delete=1
)
@pytest.mark.parametrize("field_name, field_values", FIELD_VALUE_INVALID_LIST)
def test_update_fields_invalid(
self, full_collection: Collection, field_name: str, field_values
):
for i, field_value in enumerate(field_values):
insert_doc = generate_doc(i, full_collection.schema)
singledoc_and_check(full_collection, insert_doc, is_delete=0)
update_doc_fields, update_doc_vectors = generate_vectordict_random(
full_collection.schema
)
update_doc_fields[field_name] = field_value
update_doc = Doc(
id=str(i), fields=update_doc_fields, vectors=update_doc_vectors
)
with pytest.raises(Exception) as exc_info:
full_collection.update(update_doc)
assert exc_info.value is not None
full_collection.delete(insert_doc.id)
stats = full_collection.stats
assert stats is not None
assert stats.doc_count == 0
@pytest.mark.parametrize("vector_field, vector_values", VECTOR_VALUE_VALID_LIST)
def test_update_doc_vector_valid(
self,
full_collection: Collection,
collection_temp_dir,
collection_option,
vector_field: str,
vector_values,
):
for i, vector_value in enumerate(vector_values):
insert_doc = generate_doc(i, full_collection.schema)
singledoc_and_check(full_collection, insert_doc, is_delete=0)
update_doc_fields, update_doc_vectors = generate_vectordict_random(
full_collection.schema
)
update_doc_vectors[vector_field] = vector_value
update_doc = Doc(
id=str(i), fields=update_doc_fields, vectors=update_doc_vectors
)
singledoc_and_check(full_collection, update_doc, operator="update")
@pytest.mark.parametrize("vector_field, vector_values", VECTOR_VALUE_INVALID_LIST)
def test_update_doc_vector_invalid(
self,
full_collection: Collection,
collection_temp_dir,
collection_option,
vector_field: str,
vector_values,
):
for i, vector_value in enumerate(vector_values):
insert_doc = generate_doc(i, full_collection.schema)
singledoc_and_check(full_collection, insert_doc, is_delete=0)
update_doc_fields, update_doc_vectors = generate_vectordict_random(
full_collection.schema
)
update_doc_vectors[vector_field] = vector_value
update_doc = Doc(
id=str(i), fields=update_doc_fields, vectors=update_doc_vectors
)
with pytest.raises(Exception) as exc_info:
full_collection.update(update_doc)
assert exc_info.value is not None
full_collection.delete(insert_doc.id)
stats = full_collection.stats
assert stats is not None
assert stats.doc_count == 0
@pytest.mark.parametrize(
"update_type, fields_to_update, vectors_to_update", UPDATE_PARTIAL_VALUE
)
def test_update_partial_fields(
self,
full_collection: Collection,
collection_temp_dir,
collection_option,
update_type: str,
fields_to_update: dict,
vectors_to_update: dict,
doc_id=1,
):
insert_doc = generate_doc(doc_id, full_collection.schema)
singledoc_and_check(full_collection, insert_doc, is_delete=0)
update_doc_fields, update_doc_vectors = insert_doc.fields, insert_doc.vectors
for k, v in fields_to_update.items():
update_doc_fields[k] = v
for k, v in vectors_to_update.items():
update_doc_vectors[k] = v
update_doc_full = Doc(
id=str(doc_id), fields=update_doc_fields, vectors=update_doc_vectors
)
update_doc_partial = Doc(
id=str(doc_id), fields=fields_to_update, vectors=vectors_to_update
)
updatedoc_partial_check(
full_collection,
update_doc_partial,
update_doc_full,
operator="update",
is_delete=1,
)
class TestCollectionUpsert:
def test_new_doc_upsert(self, full_collection: Collection):
single_doc = generate_doc(1, full_collection.schema)
singledoc_and_check(full_collection, single_doc, operator="upsert", is_delete=1)
@pytest.mark.parametrize("doc_num", [1, 5, Maximum])
def test_new_doc_upsert_batch(self, full_collection: Collection, doc_num):
multiple_docs = [
generate_doc(i, full_collection.schema) for i in range(doc_num)
]
batchdoc_and_check(full_collection, multiple_docs, doc_num, operator="upsert")
def test_existing_doc_upsert(self, full_collection: Collection):
insert_doc = generate_doc(1, full_collection.schema)
singledoc_and_check(full_collection, insert_doc, is_delete=0)
updated_doc = generate_update_doc(1, full_collection.schema)
singledoc_and_check(full_collection, updated_doc, operator="upsert")
@pytest.mark.parametrize("doc_id", DOCID_VALID_LIST)
def test_upsert_docid_valid(self, full_collection: Collection, doc_id):
upsert_doc = generate_doc_random(doc_id, full_collection.schema)
singledoc_and_check(full_collection, upsert_doc, operator="upsert", is_delete=1)
@pytest.mark.parametrize("doc_id", DOCID_INVALID_LIST)
def test_upsert_docid_invalid(self, full_collection: Collection, doc_id):
upsert_doc = generate_doc_random(doc_id, full_collection.schema)
with pytest.raises(Exception) as exc_info:
full_collection.upsert(upsert_doc)
assert exc_info.value is not None
stats = full_collection.stats
assert stats is not None
assert stats.doc_count == 0
@pytest.mark.parametrize("field_name, field_values", FIELD_VALUE_VALID_LIST)
@pytest.mark.parametrize(
"full_schema_new",
[(True, True, HnswIndexParam()), (False, True, HnswIndexParam())],
indirect=True,
)
def test_upsert_fields_valid(
self, full_collection_new: Collection, field_name: str, field_values, request
):
for i, field_value in enumerate(field_values):
doc_id = str(field_value) if field_name == "id" else str(i)
doc_fields, doc_vectors = generate_vectordict_random(
full_collection_new.schema
)
full_schema_params = request.getfixturevalue("full_schema_new")
target_field = None
for field in full_schema_params.fields:
if field.name == field_name:
target_field = field
break
doc_fields[field_name] = field_value
upsert_doc = Doc(id=doc_id, fields=doc_fields, vectors=doc_vectors)
if target_field and not target_field.nullable and field_value is None:
with pytest.raises(Exception) as exc_info:
full_collection_new.upsert(upsert_doc)
assert exc_info.value is not None
else:
singledoc_and_check(
full_collection_new, upsert_doc, operator="upsert", is_delete=1
)
@pytest.mark.parametrize("field_name, field_values", FIELD_VALUE_INVALID_LIST)
def test_upsert_fields_invalid(
self, full_collection: Collection, field_name: str, field_values
):
for i, field_value in enumerate(field_values):
doc_id = str(field_value) if field_name == "id" else str(i)
doc_fields, doc_vectors = generate_vectordict_random(full_collection.schema)
doc_fields[field_name] = field_value
upsert_doc = Doc(id=doc_id, fields=doc_fields, vectors=doc_vectors)
with pytest.raises(Exception) as exc_info:
full_collection.upsert(upsert_doc)
assert exc_info.value is not None
stats = full_collection.stats
assert stats is not None
assert stats.doc_count == 0
@pytest.mark.parametrize("vector_field, vector_values", VECTOR_VALUE_VALID_LIST)
def test_upsert_vector_valid(
self, full_collection: Collection, vector_field: str, vector_values
):
for i, vector_value in enumerate(vector_values):
doc_fields, doc_vectors = generate_vectordict_random(full_collection.schema)
doc_vectors[vector_field] = vector_value
upsert_doc = Doc(id=str(i), fields=doc_fields, vectors=doc_vectors)
singledoc_and_check(
full_collection, upsert_doc, operator="upsert", is_delete=1
)
@pytest.mark.parametrize("vector_field, vector_values", VECTOR_VALUE_INVALID_LIST)
def test_upsert_vector_invalid(
self, full_collection: Collection, vector_field: str, vector_values
):
for i, vector_value in enumerate(vector_values):
doc_fields, doc_vectors = generate_vectordict_random(full_collection.schema)
doc_vectors[vector_field] = vector_value
upsert_doc = Doc(id=str(i), fields=doc_fields, vectors=doc_vectors)
with pytest.raises(Exception) as exc_info:
full_collection.upsert(upsert_doc)
assert exc_info.value is not None
stats = full_collection.stats
assert stats is not None
assert stats.doc_count == 0
class TestCollectionDelete:
@pytest.mark.parametrize("doc_num", [1, 5, Maximum])
def test_delete_batch(self, full_collection: Collection, doc_num):
multiple_docs = [
generate_doc(i, full_collection.schema) for i in range(doc_num)
]
batchdoc_and_check(full_collection, multiple_docs, doc_num, operator="insert")
doc_ids = [doc.id for doc in multiple_docs]
result = full_collection.delete(doc_ids)
assert len(result) == len(doc_ids)
for item in result:
assert item.ok()
def test_delete_non_exist(self, full_collection: Collection):
result = full_collection.delete("non_existing_id")
assert result.code().value == 1
assert result.code() == StatusCode.NOT_FOUND
@pytest.mark.parametrize("doc_num", [5])
def test_delete_batch_part_non_exist(self, full_collection: Collection, doc_num):
multiple_docs = [
generate_doc(i, full_collection.schema) for i in range(doc_num)
]
batchdoc_and_check(full_collection, multiple_docs, doc_num, operator="insert")
doc_ids = [doc.id for doc in multiple_docs]
doc_ids.extend([str(doc_num), str(doc_num + 1)])
result = full_collection.delete(doc_ids)
assert len(result) == len(doc_ids)
for i in range(len(result)):
if i < doc_num:
assert result[i].ok()
else:
assert result[i].code().value == 1
assert result[i].code() == StatusCode.NOT_FOUND
@pytest.mark.parametrize("doc_num", [5])
def test_delete_by_filter(self, full_collection: Collection, doc_num):
multiple_docs = [
generate_doc(i, full_collection.schema) for i in range(doc_num)
]
batchdoc_and_check(full_collection, multiple_docs, doc_num, operator="insert")
result = full_collection.delete_by_filter("int32_field > 0")
assert result is None
def test_delete_empty_ids(self, full_collection: Collection):
result = full_collection.delete([])
assert len(result) == 0