mirror of
https://github.com/milvus-io/milvus.git
synced 2026-07-21 10:15:43 +00:00
test: cover add-field default values on growing segments (#51303)
## What this PR does - Remove the `xfail` marker from the existing drop-and-readd analyzer-field regression for #50484. - Add L1 E2E coverage for the live QueryNode growing-segment reopen path when an `enable_match` VARCHAR field with `default_value` is added to loaded growing data. - Verify the default value is immediately searchable on pre-existing unflushed rows, eventually searchable on a subsequent defaulted insert after the growing text index catches up, and remains searchable after flush/reload. Related fix: #51201 issue: #50484 ## Scope note This PR validates the live reopen path: ```text loaded collection -> insert rows that stay in a QueryNode growing segment -> add an `enable_match` VARCHAR field with `default_value` -> query triggers LazyCheckSchema/Reopen -> default values for pre-existing growing rows are indexed and searchable immediately ``` It does not cover the QueryNode recovery / LoadGrowing path. That path requires forcing a QueryNode restart, replacement, or channel rewatch while the data is still growing: the new QueryNode can create the segment with the latest schema and then load old binlogs written before AddField. Since the segment schema is already current in that path, LazyCheckSchema/Reopen is not the mechanism being tested here. Recovery coverage should be handled by a focused stability test. ## Test results - `test_milvus_client_add_match_field_with_default_value_on_growing_data`: 3/3 passed - `test_drop_then_add_same_name_analyzer_field`: passed - Ruff lint and format checks: passed - `git diff --check`: passed Signed-off-by: lyyyuna <yiyang.li@zilliz.com> Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 4.6
parent
10adefb7d4
commit
c322c84c22
@@ -930,6 +930,120 @@ class TestMilvusClientAddFieldFeature(TestMilvusClientV2Base):
|
||||
self.release_collection(client, collection_name)
|
||||
self.drop_collection(client, collection_name)
|
||||
|
||||
@pytest.mark.tags(CaseLabel.L1)
|
||||
def test_milvus_client_add_match_field_with_default_value_on_growing_data(self):
|
||||
"""
|
||||
target: verify schema reopen builds a text index for an added enable_match VARCHAR field
|
||||
and indexes its default value for pre-existing growing rows
|
||||
method: load a basic collection, insert rows without flush, add an analyzer field with default_value,
|
||||
query text_match immediately, insert another row that uses the default, then flush/reload and repeat
|
||||
expected: old rows immediately match the default token, the new row matches after the growing text index
|
||||
catches up, and all results remain unchanged after reload
|
||||
"""
|
||||
client = self._client()
|
||||
collection_name = cf.gen_collection_name_by_testcase_name()
|
||||
dim = 8
|
||||
field_name = "text_content"
|
||||
default_text = "defaultmarker"
|
||||
old_row_count = default_nb
|
||||
old_ids = set(range(old_row_count))
|
||||
|
||||
# Create and load before insert so the historical rows live in a QueryNode growing segment.
|
||||
schema = client.create_schema(enable_dynamic_field=False, auto_id=False)
|
||||
schema.add_field("id", DataType.INT64, is_primary=True)
|
||||
schema.add_field("vec", DataType.FLOAT_VECTOR, dim=dim)
|
||||
index_params = client.prepare_index_params()
|
||||
index_params.add_index(field_name="vec", index_type="AUTOINDEX", metric_type="L2")
|
||||
client.create_collection(
|
||||
collection_name=collection_name,
|
||||
schema=schema,
|
||||
index_params=index_params,
|
||||
consistency_level="Strong",
|
||||
)
|
||||
client.load_collection(collection_name)
|
||||
|
||||
vectors = cf.gen_vectors(old_row_count + 1, dim, vector_data_type=DataType.FLOAT_VECTOR)
|
||||
old_rows = [{"id": i, "vec": vectors[i]} for i in range(old_row_count)]
|
||||
client.insert(collection_name=collection_name, data=old_rows)
|
||||
|
||||
# Confirm the unflushed tail is query-visible before changing the schema.
|
||||
visible = client.query(
|
||||
collection_name,
|
||||
filter=f"id == {old_row_count - 1}",
|
||||
output_fields=["id"],
|
||||
)
|
||||
assert [row["id"] for row in visible] == [old_row_count - 1]
|
||||
|
||||
client.add_collection_field(
|
||||
collection_name,
|
||||
field_name=field_name,
|
||||
data_type=DataType.VARCHAR,
|
||||
nullable=True,
|
||||
default_value=default_text,
|
||||
max_length=64,
|
||||
enable_analyzer=True,
|
||||
enable_match=True,
|
||||
analyzer_params={"tokenizer": "standard"},
|
||||
)
|
||||
|
||||
# Critical regression assertion: do not flush, create an index, or reload before this query.
|
||||
default_match = client.query(
|
||||
collection_name,
|
||||
filter=f"text_match({field_name}, '{default_text}')",
|
||||
output_fields=["id", field_name],
|
||||
limit=old_row_count + 1,
|
||||
)
|
||||
assert {row["id"] for row in default_match} == old_ids
|
||||
assert len(default_match) == old_row_count
|
||||
assert all(row[field_name] == default_text for row in default_match)
|
||||
|
||||
new_id = old_row_count
|
||||
all_ids = set(range(old_row_count + 1))
|
||||
client.insert(
|
||||
collection_name=collection_name,
|
||||
data=[{"id": new_id, "vec": vectors[new_id]}],
|
||||
)
|
||||
|
||||
inserted = client.query(
|
||||
collection_name,
|
||||
filter=f"id == {new_id}",
|
||||
output_fields=["id", field_name],
|
||||
)
|
||||
assert [row["id"] for row in inserted] == [new_id]
|
||||
assert inserted[0][field_name] == default_text
|
||||
|
||||
# Growing text index updates asynchronously for newly inserted rows. Poll with a bounded timeout.
|
||||
default_after_insert = []
|
||||
for _ in range(30):
|
||||
default_after_insert = client.query(
|
||||
collection_name,
|
||||
filter=f"text_match({field_name}, '{default_text}')",
|
||||
output_fields=["id", field_name],
|
||||
limit=old_row_count + 1,
|
||||
)
|
||||
if {row["id"] for row in default_after_insert} == all_ids:
|
||||
break
|
||||
time.sleep(1)
|
||||
assert {row["id"] for row in default_after_insert} == all_ids
|
||||
assert len(default_after_insert) == old_row_count + 1
|
||||
assert all(row[field_name] == default_text for row in default_after_insert)
|
||||
|
||||
client.flush(collection_name)
|
||||
client.release_collection(collection_name)
|
||||
client.load_collection(collection_name)
|
||||
|
||||
default_after_reload = client.query(
|
||||
collection_name,
|
||||
filter=f"text_match({field_name}, '{default_text}')",
|
||||
output_fields=["id", field_name],
|
||||
limit=old_row_count + 1,
|
||||
)
|
||||
assert {row["id"] for row in default_after_reload} == all_ids
|
||||
assert len(default_after_reload) == old_row_count + 1
|
||||
assert all(row[field_name] == default_text for row in default_after_reload)
|
||||
|
||||
client.drop_collection(collection_name)
|
||||
|
||||
@pytest.mark.tags(CaseLabel.L1)
|
||||
def test_milvus_client_add_field_and_update_existing_data(self):
|
||||
"""
|
||||
|
||||
@@ -2008,10 +2008,6 @@ class TestMilvusClientDropFieldFeature(TestMilvusClientV2Base):
|
||||
self.drop_collection(client, collection_name)
|
||||
|
||||
@pytest.mark.tags(CaseLabel.L1)
|
||||
@pytest.mark.xfail(
|
||||
reason="Known issue: https://github.com/milvus-io/milvus/issues/50484",
|
||||
strict=False,
|
||||
)
|
||||
def test_drop_then_add_same_name_analyzer_field(self):
|
||||
"""
|
||||
TC-L1-14: Drop analyzer field and re-add same-name field with different analyzer params.
|
||||
|
||||
Reference in New Issue
Block a user