forked from milvus-io/milvus
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathtest_milvus_client_collection.py
More file actions
6760 lines (6331 loc) · 317 KB
/
Copy pathtest_milvus_client_collection.py
File metadata and controls
6760 lines (6331 loc) · 317 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
import threading
import time
import numpy as np
import pytest
from base.client_v2_base import TestMilvusClientV2Base
from common import common_func as cf
from common import common_type as ct
from common.common_type import CaseLabel, CheckTasks
from pymilvus import DataType
from pymilvus.client.types import LoadState
from utils.util_log import test_log as log
from utils.util_pymilvus import MyThread
prefix = "client_collection"
epsilon = ct.epsilon
default_nb = ct.default_nb
default_nb_medium = ct.default_nb_medium
default_nq = ct.default_nq
default_dim = ct.default_dim
default_limit = ct.default_limit
default_search_exp = "id >= 0"
exp_res = "exp_res"
default_search_string_exp = 'varchar >= "0"'
default_search_mix_exp = 'int64 >= 0 && varchar >= "0"'
default_invaild_string_exp = "varchar >= 0"
default_json_search_exp = 'json_field["number"] >= 0'
perfix_expr = 'varchar like "0%"'
default_search_field = ct.default_float_vec_field_name
default_search_params = ct.default_search_params
default_primary_key_field_name = "id"
default_vector_field_name = "vector"
default_float_field_name = ct.default_float_field_name
default_bool_field_name = ct.default_bool_field_name
default_string_field_name = ct.default_string_field_name
default_int32_array_field_name = ct.default_int32_array_field_name
default_string_array_field_name = ct.default_string_array_field_name
class TestMilvusClientCollectionInvalid(TestMilvusClientV2Base):
"""Test case of create collection interface"""
@pytest.fixture(scope="function", params=[False, True])
def auto_id(self, request):
yield request.param
@pytest.fixture(scope="function", params=["COSINE", "L2"])
def metric_type(self, request):
yield request.param
"""
******************************************************************
# The following are invalid base cases
******************************************************************
"""
@pytest.mark.tags(CaseLabel.L1)
@pytest.mark.parametrize(
"collection_name", ["12-s", "12 s", "(mn)", "中文", "%$#", "español", "عربي", "हिंदी", "Русский"]
)
def test_milvus_client_collection_invalid_collection_name(self, collection_name):
"""
target: test fast create collection with invalid collection name
method: create collection with invalid collection
expected: raise exception
"""
client = self._client()
# 1. create collection
if collection_name == "español":
expected_msg = "collection name can only contain numbers, letters and underscores"
else:
expected_msg = "the first character of a collection name must be an underscore or letter"
error = {
ct.err_code: 1100,
ct.err_msg: f"Invalid collection name: {collection_name}. {expected_msg}: invalid parameter",
}
self.create_collection(client, collection_name, default_dim, check_task=CheckTasks.err_res, check_items=error)
@pytest.mark.tags(CaseLabel.L1)
def test_milvus_client_collection_name_over_max_length(self):
"""
target: test fast create collection with over max collection name length
method: create collection with over max collection name length
expected: raise exception
"""
client = self._client()
# 1. create collection
collection_name = "a".join("a" for i in range(256))
error = {ct.err_code: 1100, ct.err_msg: "the length of a collection name must be less than 255 characters"}
self.create_collection(client, collection_name, default_dim, check_task=CheckTasks.err_res, check_items=error)
@pytest.mark.tags(CaseLabel.L1)
def test_milvus_client_collection_name_empty(self):
"""
target: test fast create collection name with empty
method: create collection name with empty
expected: raise exception
"""
client = self._client()
# 1. create collection
collection_name = " "
error = {ct.err_code: 1100, ct.err_msg: "Invalid collection name"}
self.create_collection(client, collection_name, default_dim, check_task=CheckTasks.err_res, check_items=error)
@pytest.mark.tags(CaseLabel.L1)
@pytest.mark.parametrize("invalid_dim", ct.invalid_dims)
def test_milvus_client_collection_vector_invalid_dim_default_schema(self, invalid_dim):
"""
target: Test collection with invalid vector dimension
method: Create collection with vector field having invalid dimension
expected: Raise exception with appropriate error message
"""
client = self._client()
collection_name = cf.gen_collection_name_by_testcase_name()
# Determine expected error based on invalid dimension type
if isinstance(invalid_dim, int) and (invalid_dim > 32768):
expected_msg = f"invalid dimension: {invalid_dim} of field {default_vector_field_name}. float vector dimension should be in range 2 ~ 32768"
elif isinstance(invalid_dim, int) and (invalid_dim < 2): # range errors: 1, -32
expected_msg = f"invalid dimension: {invalid_dim}. should be in range 2 ~ 32768"
elif isinstance(invalid_dim, str): # type conversion errors: "vii", "十六"
expected_msg = "wrong type of argument [dimension], expected type: [int], got type: [str]"
elif isinstance(invalid_dim, float): # type conversion errors: 32.1
expected_msg = "wrong type of argument [dimension], expected type: [int], got type: [float]"
# Try to create collection and expect error
error = {ct.err_code: 65535, ct.err_msg: expected_msg}
self.create_collection(client, collection_name, invalid_dim, check_task=CheckTasks.err_res, check_items=error)
@pytest.mark.tags(CaseLabel.L2)
@pytest.mark.skip(reason="pymilvus issue 1554")
def test_milvus_client_collection_invalid_primary_field(self):
"""
target: test fast create collection name with invalid primary field
method: create collection name with invalid primary field
expected: raise exception
"""
client = self._client()
collection_name = cf.gen_unique_str(prefix)
# 1. create collection
error = {ct.err_code: 1, ct.err_msg: "Param id_type must be int or string"}
self.create_collection(
client, collection_name, default_dim, id_type="invalid", check_task=CheckTasks.err_res, check_items=error
)
@pytest.mark.tags(CaseLabel.L2)
def test_milvus_client_collection_string_auto_id(self):
"""
target: test creating a collection with string primary key and auto_id but without specifying max_length
method: attempt to create collection with string primary key and auto_id=True, omitting max_length
expected: raise exception due to missing max_length for string primary key
"""
client = self._client()
collection_name = cf.gen_collection_name_by_testcase_name()
# 1. create collection
error = {
ct.err_code: 65535,
ct.err_msg: f"type param(max_length) should be specified for the field(id) of collection {collection_name}",
}
self.create_collection(
client,
collection_name,
default_dim,
id_type="string",
auto_id=True,
check_task=CheckTasks.err_res,
check_items=error,
)
@pytest.mark.tags(CaseLabel.L2)
@pytest.mark.parametrize("auto_id", [None, 1, "string"])
def test_collection_auto_id_invalid_types(self, auto_id):
"""
target: test collection creation with invalid auto_id types
method: attempt to create a collection with auto_id set to non-bool values
expected: raise exception indicating auto_id must be bool
"""
client = self._client()
collection_name = cf.gen_collection_name_by_testcase_name()
# Attempt to create a collection with invalid auto_id
error = {ct.err_code: 0, ct.err_msg: "Param auto_id must be bool type"}
self.create_collection(
client, collection_name, default_dim, auto_id=auto_id, check_task=CheckTasks.err_res, check_items=error
)
@pytest.mark.tags(CaseLabel.L2)
def test_milvus_client_collection_auto_id_none_in_field(self):
"""
target: test collection with auto_id set to None in field definition
method: try to create a collection with a primary key field where auto_id=None
expected: raise exception indicating auto_id must be bool
"""
client = self._client()
# Create schema and try to add field with auto_id=None - this should raise exception
schema = self.create_schema(client, enable_dynamic_field=False)[0]
error = {ct.err_code: 0, ct.err_msg: "Param auto_id must be bool type"}
self.add_field(
schema,
ct.default_int64_field_name,
DataType.INT64,
is_primary=True,
auto_id=None,
check_task=CheckTasks.err_res,
check_items=error,
)
@pytest.mark.tags(CaseLabel.L2)
def test_milvus_client_collection_multi_fields_auto_id(self):
"""
target: test collection auto_id with multi fields (non-primary field with auto_id)
method: specify auto_id=True for a non-primary int64 field
expected: raise exception indicating auto_id can only be specified on primary key field
"""
client = self._client()
# Create schema and try to add non-primary field with auto_id=True - this should raise exception
schema = self.create_schema(client, enable_dynamic_field=False)[0]
# Add primary key field
schema.add_field(ct.default_int64_field_name, DataType.INT64, is_primary=True, auto_id=True)
# Test that adding a non-primary field with auto_id=True raises exception
error = {ct.err_code: 0, ct.err_msg: "auto_id can only be specified on the primary key field"}
self.add_field(
schema, "int_field", DataType.INT64, auto_id=True, check_task=CheckTasks.err_res, check_items=error
)
@pytest.mark.tags(CaseLabel.L2)
def test_milvus_client_collection_auto_id_non_primary_field(self):
"""
target: test collection set auto_id in non-primary field
method: set auto_id=True in non-primary field directly
expected: raise exception indicating auto_id can only be specified on primary key field
"""
client = self._client()
# Create schema and try to add non-primary field with auto_id=True - this should raise exception
schema = self.create_schema(client, enable_dynamic_field=False)[0]
# Test that creating a non-primary field with auto_id=True raises exception
error = {ct.err_code: 999, ct.err_msg: "auto_id can only be specified on the primary key field"}
self.add_field(
schema,
ct.default_int64_field_name,
DataType.INT64,
auto_id=True,
check_task=CheckTasks.err_res,
check_items=error,
)
@pytest.mark.tags(CaseLabel.L1)
def test_milvus_client_create_collection_dup_name_different_params(self):
"""
target: test create same collection with different parameters
method: create same collection with different dims, schemas, and primary fields
expected: raise exception for all different parameter cases
"""
client = self._client()
collection_name = cf.gen_collection_name_by_testcase_name()
self.create_collection(client, collection_name, default_dim)
# Test 1: Different dimensions
error = {
ct.err_code: 1,
ct.err_msg: f"create duplicate collection with different parameters, collection: {collection_name}",
}
self.create_collection(
client, collection_name, default_dim + 1, check_task=CheckTasks.err_res, check_items=error
)
# Test 2: Different schemas
schema_diff = self.create_schema(client, enable_dynamic_field=False)[0]
schema_diff.add_field("new_id", DataType.VARCHAR, max_length=64, is_primary=True, auto_id=False)
schema_diff.add_field("new_vector", DataType.FLOAT_VECTOR, dim=128)
self.create_collection(
client, collection_name, schema=schema_diff, check_task=CheckTasks.err_res, check_items=error
)
# Test 3: Different primary fields
schema2 = self.create_schema(client, enable_dynamic_field=False)[0]
schema2.add_field("id_2", DataType.INT64, is_primary=True, auto_id=False)
schema2.add_field("vector", DataType.FLOAT_VECTOR, dim=default_dim)
self.create_collection(
client, collection_name, schema=schema2, check_task=CheckTasks.err_res, check_items=error
)
# Verify original collection's primary field is unchanged
self.describe_collection(
client,
collection_name,
check_task=CheckTasks.check_describe_collection_property,
check_items={"collection_name": collection_name, "dim": default_dim, "id_name": "id"},
)
self.drop_collection(client, collection_name)
@pytest.mark.tags(CaseLabel.L2)
@pytest.mark.parametrize("metric_type", [1, " ", "invalid"])
def test_milvus_client_collection_invalid_metric_type(self, metric_type):
"""
target: test create same collection with invalid metric type
method: create same collection with invalid metric type
expected: raise exception
"""
client = self._client()
collection_name = cf.gen_collection_name_by_testcase_name()
# 1. create collection
error = {
ct.err_code: 1100,
ct.err_msg: f"float vector index does not support metric type: {metric_type}: "
f"invalid parameter[expected=valid index params][actual=invalid index params",
}
self.create_collection(
client,
collection_name,
default_dim,
metric_type=metric_type,
check_task=CheckTasks.err_res,
check_items=error,
)
@pytest.mark.tags(CaseLabel.L2)
@pytest.mark.skip(reason="pymilvus issue 1864")
def test_milvus_client_collection_invalid_schema_field_name(self):
"""
target: test create collection with invalid schema field name
method: create collection with invalid schema field name
expected: raise exception
"""
client = self._client()
collection_name = cf.gen_unique_str(prefix)
schema = self.create_schema(client, enable_dynamic_field=False)[0]
schema.add_field("%$#", DataType.VARCHAR, max_length=64, is_primary=True, auto_id=False)
schema.add_field("embeddings", DataType.FLOAT_VECTOR, dim=128)
# 1. create collection
error = {
ct.err_code: 65535,
ct.err_msg: "metric type not found or not supported, supported: [L2 IP COSINE HAMMING JACCARD]",
}
self.create_collection(client, collection_name, schema=schema, check_task=CheckTasks.err_res, check_items=error)
@pytest.mark.tags(CaseLabel.L2)
@pytest.mark.parametrize("dtype", [6, [[]], "int64", 5.1, (), "", "a", DataType.UNKNOWN])
def test_milvus_client_collection_invalid_field_type(self, dtype):
"""
target: test collection with invalid field type
method: try to add a field with an invalid DataType to schema
expected: raise exception
"""
client = self._client()
schema = self.create_schema(client, enable_dynamic_field=False)[0]
# Try to add a field with invalid dtype
error = {ct.err_code: 999, ct.err_msg: "Field dtype must be of DataType"}
# The add_field method should raise an error for invalid dtype
self.add_field(schema, field_name="test", datatype=dtype, check_task=CheckTasks.err_res, check_items=error)
@pytest.mark.tags(CaseLabel.L2)
@pytest.mark.parametrize(
"unsupported_field_type",
[
DataType.NONE,
DataType.BOOL,
DataType.INT8,
DataType.INT16,
DataType.INT32,
DataType.FLOAT,
DataType.DOUBLE,
DataType.STRING,
DataType.JSON,
DataType.ARRAY,
DataType.GEOMETRY,
DataType.FLOAT_VECTOR,
DataType.BINARY_VECTOR,
DataType.SPARSE_FLOAT_VECTOR,
DataType.INT8_VECTOR,
DataType.FLOAT16_VECTOR,
DataType.BFLOAT16_VECTOR,
],
)
def test_milvus_client_collection_unsupported_primary_field(self, unsupported_field_type):
"""
target: test collection with unsupported primary field type
method: create collection with unsupported primary field type
expected: raise exception when creating collection
"""
client = self._client()
collection_name = cf.gen_collection_name_by_testcase_name()
# Create schema with unsupported primary field type
schema = self.create_schema(client, enable_dynamic_field=False)[0]
if unsupported_field_type in [
DataType.FLOAT_VECTOR,
DataType.BINARY_VECTOR,
DataType.INT8_VECTOR,
DataType.FLOAT16_VECTOR,
DataType.BFLOAT16_VECTOR,
]:
schema.add_field("unsupported_primary", unsupported_field_type, is_primary=True, dim=default_dim)
elif unsupported_field_type == DataType.SPARSE_FLOAT_VECTOR:
schema.add_field("unsupported_primary", unsupported_field_type, is_primary=True)
elif unsupported_field_type == DataType.ARRAY:
schema.add_field(
"unsupported_primary",
unsupported_field_type,
is_primary=True,
element_type=DataType.INT64,
max_capacity=100,
)
else:
schema.add_field("unsupported_primary", unsupported_field_type, is_primary=True)
schema.add_field("vector_field", DataType.FLOAT_VECTOR, dim=default_dim)
# Try to create collection - should fail here
error = {ct.err_code: 1100, ct.err_msg: "Primary key type must be DataType.INT64 or DataType.VARCHAR"}
self.create_collection(client, collection_name, schema=schema, check_task=CheckTasks.err_res, check_items=error)
@pytest.mark.tags(CaseLabel.L2)
@pytest.mark.parametrize("invalid_name", ["中文", "español", "عربي", "हिंदी", "Русский", "!@#$%^&*()", "123abc"])
def test_milvus_client_collection_schema_with_invalid_field_name(self, invalid_name):
"""
target: test create collection schema with invalid field names
method: try to create a schema with a field name
expected: raise exception
"""
client = self._client()
collection_name = cf.gen_collection_name_by_testcase_name()
schema = self.create_schema(client, enable_dynamic_field=False)[0]
schema.add_field("id", DataType.INT64, is_primary=True, auto_id=False)
schema.add_field("vector", DataType.FLOAT_VECTOR, dim=default_dim)
# Add a field with an invalid name
schema.add_field(invalid_name, DataType.VARCHAR, max_length=128)
# Determine expected error message based on invalid field name type
if invalid_name == "español":
expected_msg = "Field name can only contain numbers, letters, and underscores."
else:
expected_msg = "The first character of a field name must be an underscore or letter."
error = {
ct.err_code: 1701,
ct.err_msg: f"Invalid field name: {invalid_name}. {expected_msg}: field name invalid[field={invalid_name}]",
}
self.create_collection(client, collection_name, schema=schema, check_task=CheckTasks.err_res, check_items=error)
@pytest.mark.tags(CaseLabel.L1)
@pytest.mark.parametrize(
"keyword",
[
"$meta",
"like",
"exists",
"EXISTS",
"and",
"or",
"not",
"in",
"json_contains",
"JSON_CONTAINS",
"json_contains_all",
"JSON_CONTAINS_ALL",
"json_contains_any",
"JSON_CONTAINS_ANY",
"array_contains",
"ARRAY_CONTAINS",
"array_contains_all",
"ARRAY_CONTAINS_ALL",
"array_contains_any",
"ARRAY_CONTAINS_ANY",
"array_length",
"ARRAY_LENGTH",
"true",
"True",
"TRUE",
"false",
"False",
"FALSE",
"text_match",
"TEXT_MATCH",
"phrase_match",
"PHRASE_MATCH",
"random_sample",
"RANDOM_SAMPLE",
],
)
def test_milvus_client_collection_field_name_with_keywords(self, keyword):
"""
target: test collection creation with field name using Milvus keywords
method: create collection with field name using reserved keywords
expected: raise exception
"""
client = self._client()
collection_name = cf.gen_collection_name_by_testcase_name()
# Create schema with field name using reserved keyword
schema = self.create_schema(client, enable_dynamic_field=False)[0]
schema.add_field("id", DataType.INT64, is_primary=True, auto_id=False)
schema.add_field(keyword, DataType.FLOAT_VECTOR, dim=default_dim)
# Attempt to create collection with invalid field name - should fail
error = {ct.err_code: 1701, ct.err_msg: f"Invalid field name: {keyword}"}
self.create_collection(client, collection_name, schema=schema, check_task=CheckTasks.err_res, check_items=error)
@pytest.mark.tags(CaseLabel.L2)
def test_milvus_client_collection_empty_fields(self):
"""
target: test create collection with empty fields
method: create collection with schema that has no fields
expected: raise exception
"""
client = self._client()
collection_name = cf.gen_collection_name_by_testcase_name()
# Create an empty schema (no fields added)
schema = self.create_schema(client, enable_dynamic_field=False)[0]
error = {ct.err_code: 1100, ct.err_msg: "Schema must have a primary key field"}
self.create_collection(client, collection_name, schema=schema, check_task=CheckTasks.err_res, check_items=error)
@pytest.mark.tags(CaseLabel.L2)
def test_milvus_client_collection_over_maximum_limits(self):
"""
target: combine validations for all over-maximum scenarios
method:
- Scenario 1: over maximum total fields
- Scenario 2: over maximum vector fields
- Scenario 3: multiple vector fields and over maximum total fields
- Scenario 4: over maximum vector fields and over maximum total fields
expected: each scenario raises the same errors as in the original individual tests
"""
client = self._client()
collection_name = cf.gen_collection_name_by_testcase_name()
# ========== Scenario 1: over maximum total fields ==========
schema_1 = self.create_schema(client, enable_dynamic_field=False)[0]
schema_1.add_field(ct.default_int64_field_name, DataType.INT64, is_primary=True)
schema_1.add_field(default_vector_field_name, DataType.FLOAT_VECTOR, dim=default_dim)
limit_num = ct.max_field_num - 2
for _ in range(limit_num):
schema_1.add_field(cf.gen_unique_str("field_name"), DataType.INT64)
schema_1.add_field(cf.gen_unique_str("extra_field"), DataType.INT64)
error_fields_over = {ct.err_code: 1, ct.err_msg: "maximum field's number should be limited to 64"}
self.create_collection(
client,
collection_name,
default_dim,
schema=schema_1,
check_task=CheckTasks.err_res,
check_items=error_fields_over,
)
# ========== Scenario 2: over maximum vector fields ==========
schema_2 = self.create_schema(client, enable_dynamic_field=False)[0]
for _ in range(ct.max_vector_field_num + 1):
schema_2.add_field(cf.gen_unique_str("vector_field_name"), DataType.FLOAT_VECTOR, dim=default_dim)
schema_2.add_field(ct.default_int64_field_name, DataType.INT64, is_primary=True)
error_vector_over = {
ct.err_code: 65535,
ct.err_msg: f"maximum vector field's number should be limited to {ct.max_vector_field_num}",
}
self.create_collection(
client,
collection_name,
default_dim,
schema=schema_2,
check_task=CheckTasks.err_res,
check_items=error_vector_over,
)
# ========== Scenario 3: multiple vector fields and over maximum total fields ==========
schema_3 = self.create_schema(client, enable_dynamic_field=False)[0]
vector_limit_num = ct.max_vector_field_num - 2
for _ in range(vector_limit_num):
schema_3.add_field(cf.gen_unique_str("field_name"), DataType.FLOAT_VECTOR, dim=default_dim)
for _ in range(ct.max_field_num):
schema_3.add_field(cf.gen_unique_str("field_name"), DataType.INT64)
schema_3.add_field(ct.default_int64_field_name, DataType.INT64, is_primary=True)
error_fields_over_64 = {ct.err_code: 65535, ct.err_msg: "maximum field's number should be limited to 64"}
self.create_collection(
client,
collection_name,
default_dim,
schema=schema_3,
check_task=CheckTasks.err_res,
check_items=error_fields_over_64,
)
# ========== Scenario 4: over maximum vector fields and over maximum total fields ==========
schema_4 = self.create_schema(client, enable_dynamic_field=False)[0]
for _ in range(ct.max_vector_field_num + 1):
schema_4.add_field(cf.gen_unique_str("field_name"), DataType.FLOAT_VECTOR, dim=default_dim)
for _ in range(limit_num - 4):
schema_4.add_field(cf.gen_unique_str("field_name"), DataType.INT64)
schema_4.add_field(cf.gen_unique_str("field_name"), DataType.FLOAT_VECTOR, dim=default_dim)
schema_4.add_field(ct.default_int64_field_name, DataType.INT64, is_primary=True)
self.create_collection(
client,
collection_name,
default_dim,
schema=schema_4,
check_task=CheckTasks.err_res,
check_items=error_fields_over_64,
)
@pytest.mark.tags(CaseLabel.L0)
def test_milvus_client_collection_without_vectors(self):
"""
target: test create collection without vectors
method: create collection only with int field
expected: raise exception
"""
client = self._client()
collection_name = cf.gen_collection_name_by_testcase_name()
# Create schema with only non-vector fields
schema = self.create_schema(client, enable_dynamic_field=False)[0]
schema.add_field("int_field", DataType.INT64, is_primary=True, auto_id=False)
error = {ct.err_code: 1100, ct.err_msg: "schema does not contain vector field: invalid parameter"}
self.create_collection(client, collection_name, schema=schema, check_task=CheckTasks.err_res, check_items=error)
@pytest.mark.tags(CaseLabel.L2)
@pytest.mark.parametrize("vector_type", [DataType.FLOAT_VECTOR, DataType.BINARY_VECTOR])
def test_milvus_client_collection_vector_without_dim(self, vector_type):
"""
target: test creating a collection with a vector field missing the dimension
method: define a vector field without specifying dim and attempt to create the collection
expected: raise exception
"""
client = self._client()
collection_name = cf.gen_collection_name_by_testcase_name()
# Create schema with a vector field missing the dim parameter
schema = self.create_schema(client, enable_dynamic_field=False)[0]
schema.add_field("id", DataType.INT64, is_primary=True, auto_id=False)
# Add vector field without dim
schema.add_field("vector_field", vector_type)
error = {ct.err_code: 1, ct.err_msg: "dimension is not defined in field type params"}
self.create_collection(client, collection_name, schema=schema, check_task=CheckTasks.err_res, check_items=error)
@pytest.mark.tags(CaseLabel.L1)
@pytest.mark.parametrize("vector_type", [DataType.FLOAT_VECTOR, DataType.INT8_VECTOR, DataType.BINARY_VECTOR])
def test_milvus_client_collection_without_primary_field(self, vector_type):
"""
target: test create collection without primary field
method: no primary field specified in collection schema and fields
expected: raise exception
"""
client = self._client()
collection_name = cf.gen_collection_name_by_testcase_name()
# Create schema with fields but no primary key
schema1 = self.create_schema(client, enable_dynamic_field=False)[0]
schema1.add_field("int_field", DataType.INT64) # Not primary
schema1.add_field("vector_field", vector_type, dim=default_dim)
error = {ct.err_code: 1100, ct.err_msg: "Schema must have a primary key field"}
self.create_collection(
client, collection_name, schema=schema1, check_task=CheckTasks.err_res, check_items=error
)
# Create schema with only vector field
schema2 = self.create_schema(client, enable_dynamic_field=False)[0]
schema2.add_field("vector_field", vector_type, dim=default_dim)
error = {ct.err_code: 1100, ct.err_msg: "Schema must have a primary key field"}
self.create_collection(
client, collection_name, schema=schema2, check_task=CheckTasks.err_res, check_items=error
)
@pytest.mark.tags(CaseLabel.L2)
@pytest.mark.parametrize("primary_field", [[], 1, [1, "2", 3], (1,), {1: 1}])
def test_milvus_client_collection_non_string_primary_field(self, primary_field):
"""
target: test collection with non-string primary_field
method: pass a non-string/non-int value as primary_field to schema creation
expected: raise exception
"""
client = self._client()
# Test at schema creation level - create schema with invalid primary_field parameter
error = {ct.err_code: 999, ct.err_msg: "Param primary_field must be int or str type"}
# This should fail when creating schema with invalid primary_field type
self.create_schema(
client,
enable_dynamic_field=False,
primary_field=primary_field,
check_task=CheckTasks.err_res,
check_items=error,
)
@pytest.mark.tags(CaseLabel.L2)
@pytest.mark.parametrize("is_primary", [None, 2, "string"])
def test_milvus_client_collection_invalid_is_primary(self, is_primary):
"""
target: test collection with invalid is_primary value
method: define a field with is_primary set to a non-bool value and attempt to create a collection
expected: raise exception indicating is_primary must be bool type
"""
client = self._client()
# Create schema and attempt to add a field with invalid is_primary value
schema = self.create_schema(client, enable_dynamic_field=False)[0]
error = {ct.err_code: 999, ct.err_msg: "Param is_primary must be bool type"}
# Attempt to add a field with invalid is_primary value, expect error
self.add_field(
schema, "id", DataType.INT64, is_primary=is_primary, check_task=CheckTasks.err_res, check_items=error
)
@pytest.mark.tags(CaseLabel.L1)
def test_milvus_client_collection_dup_field(self):
"""
target: test create collection with duplicate field names
method: create schema with two fields having the same name
expected: raise exception
"""
client = self._client()
collection_name = cf.gen_collection_name_by_testcase_name()
# Create schema with duplicate field names
schema = self.create_schema(client, enable_dynamic_field=False)[0]
schema.add_field("int64_field", DataType.INT64, is_primary=True, auto_id=False, max_length=1000)
schema.add_field("float_field", DataType.FLOAT, max_length=1000)
schema.add_field("float_field", DataType.INT64, max_length=1000)
schema.add_field("vector_field", DataType.FLOAT_VECTOR, dim=default_dim)
error = {ct.err_code: 1100, ct.err_msg: "duplicated field name"}
self.create_collection(client, collection_name, schema=schema, check_task=CheckTasks.err_res, check_items=error)
has_collection = self.has_collection(client, collection_name)[0]
assert not has_collection
@pytest.mark.tags(CaseLabel.L2)
def test_milvus_client_collection_add_field_as_primary(self):
"""
target: test fast create collection with add new field as primary
method: create collection name with add new field as primary
expected: raise exception
"""
client = self._client()
collection_name = cf.gen_collection_name_by_testcase_name()
# 1. create collection
dim, field_name = 8, "field_new"
error = {
ct.err_code: 1100,
ct.err_msg: f"not support to add pk field, field name = {field_name}: invalid parameter",
}
self.create_collection(client, collection_name, dim)
collections = self.list_collections(client)[0]
assert collection_name in collections
self.add_collection_field(
client,
collection_name,
field_name=field_name,
data_type=DataType.INT64,
nullable=True,
is_primary=True,
check_task=CheckTasks.err_res,
check_items=error,
)
@pytest.mark.tags(CaseLabel.L2)
def test_milvus_client_collection_none_desc(self):
"""
target: test create collection with none description
method: create collection with none description in schema
expected: raise exception due to invalid description type
"""
client = self._client()
collection_name = cf.gen_collection_name_by_testcase_name()
# Try to create schema with None description
schema = self.create_schema(client, enable_dynamic_field=False, description=None)[0]
schema.add_field("id", DataType.INT64, is_primary=True, auto_id=False)
schema.add_field("embeddings", DataType.FLOAT_VECTOR, dim=default_dim)
error = {ct.err_code: 1100, ct.err_msg: "description [None] has type NoneType, but expected one of: bytes, str"}
self.create_collection(client, collection_name, schema=schema, check_task=CheckTasks.err_res, check_items=error)
@pytest.mark.tags(CaseLabel.L2)
def test_milvus_client_collection_invalid_schema_multi_pk(self):
"""
target: test create collection with schema containing multiple primary key fields
method: create schema with two primary key fields and use it to create collection
expected: raise exception due to multiple primary keys
"""
client = self._client()
collection_name = cf.gen_collection_name_by_testcase_name()
# Create schema with multiple primary key fields
schema_1 = self.create_schema(client, enable_dynamic_field=False)[0]
schema_1.add_field("field1", DataType.INT64, is_primary=True, auto_id=False)
schema_1.add_field("field2", DataType.INT64, is_primary=True, auto_id=False) # Second primary key
schema_1.add_field("vector_field", DataType.FLOAT_VECTOR, dim=32)
# Try to create collection with multiple primary keys
error = {ct.err_code: 999, ct.err_msg: "Expected only one primary key field"}
self.create_collection(
client, collection_name, schema=schema_1, check_task=CheckTasks.err_res, check_items=error
)
schema_2 = self.create_schema(client, enable_dynamic_field=False, primary_field="field2")[0]
schema_2.add_field("field1", DataType.INT64, is_primary=True, auto_id=False)
schema_2.add_field("field2", DataType.INT64) # Second primary key
schema_2.add_field("vector_field", DataType.FLOAT_VECTOR, dim=32)
# Try to create collection with multiple primary keys
error = {ct.err_code: 999, ct.err_msg: "Expected only one primary key field"}
self.create_collection(
client, collection_name, schema=schema_2, check_task=CheckTasks.err_res, check_items=error
)
@pytest.mark.tags(CaseLabel.L2)
@pytest.mark.parametrize(
"shards_num,error_type", [(ct.max_shards_num + 1, "range"), (257, "range"), (1.0, "type"), ("2", "type")]
)
def test_milvus_client_collection_invalid_shards(self, shards_num, error_type):
"""
target: test collection with invalid shards_num values
method: create collection with shards_num that are out of valid range or wrong type
expected: raise exception with appropriate error message
"""
client = self._client()
collection_name = cf.gen_collection_name_by_testcase_name()
if error_type == "range":
error = {ct.err_code: 1, ct.err_msg: f"maximum shards's number should be limited to {ct.max_shards_num}"}
else: # error_type == "type"
error = {ct.err_code: 999, ct.err_msg: "invalid num_shards type"}
# Try to create collection with invalid shards_num (should fail)
self.create_collection(
client,
collection_name,
default_dim,
shards_num=shards_num,
check_task=CheckTasks.err_res,
check_items=error,
)
class TestMilvusClientCollectionValid(TestMilvusClientV2Base):
"""Test case of create collection interface"""
@pytest.fixture(scope="function", params=[False, True])
def auto_id(self, request):
yield request.param
@pytest.fixture(scope="function", params=["COSINE", "L2", "IP"])
def metric_type(self, request):
yield request.param
@pytest.fixture(scope="function", params=["int", "string"])
def id_type(self, request):
yield request.param
"""
******************************************************************
# The following are valid base cases
******************************************************************
"""
@pytest.mark.tags(CaseLabel.L0)
@pytest.mark.parametrize("dim", [ct.min_dim, default_dim, ct.max_dim])
def test_milvus_client_collection_fast_creation_default(self, dim):
"""
target: test fast create collection normal case
method: create collection
expected: create collection with default schema, index, and load successfully
"""
client = self._client()
collection_name = cf.gen_unique_str(prefix)
self.using_database(client, "default")
# 1. create collection
self.create_collection(client, collection_name, dim)
collections = self.list_collections(client)[0]
assert collection_name in collections
self.describe_collection(
client,
collection_name,
check_task=CheckTasks.check_describe_collection_property,
check_items={"collection_name": collection_name, "dim": dim, "consistency_level": 0},
)
index = self.list_indexes(client, collection_name)[0]
assert index == ["vector"]
# load_state = self.get_load_state(collection_name)[0]
self.load_partitions(client, collection_name, "_default")
self.release_partitions(client, collection_name, "_default")
if self.has_collection(client, collection_name)[0]:
self.drop_collection(client, collection_name)
@pytest.mark.tags(CaseLabel.L1)
@pytest.mark.parametrize("dim", [ct.min_dim, default_dim, ct.max_dim])
def test_milvus_client_collection_fast_creation_all_params(self, dim, metric_type, id_type, auto_id):
"""
target: test fast create collection normal case
method: create collection
expected: create collection with default schema, index, and load successfully
"""
client = self._client()
collection_name = cf.gen_unique_str(prefix)
max_length = 100
# 1. create collection
self.create_collection(
client,
collection_name,
dim,
id_type=id_type,
metric_type=metric_type,
auto_id=auto_id,
max_length=max_length,
)
collections = self.list_collections(client)[0]
assert collection_name in collections
self.describe_collection(
client,
collection_name,
check_task=CheckTasks.check_describe_collection_property,
check_items={"collection_name": collection_name, "dim": dim, "auto_id": auto_id, "consistency_level": 0},
)
index = self.list_indexes(client, collection_name)[0]
assert index == ["vector"]
# load_state = self.get_load_state(collection_name)[0]
self.release_collection(client, collection_name)
self.drop_collection(client, collection_name)
@pytest.mark.tags(CaseLabel.L0)
@pytest.mark.parametrize("nullable", [True, False])
@pytest.mark.parametrize("vector_type", [DataType.FLOAT_VECTOR, DataType.INT8_VECTOR])
@pytest.mark.parametrize("add_field", [True, False])
def test_milvus_client_collection_self_creation_default(self, nullable, vector_type, add_field):
"""
target: test self create collection normal case
method: create collection
expected: create collection with default schema, index, and load successfully
"""
client = self._client()
collection_name = cf.gen_unique_str(prefix)
dim = 128
# 1. create collection
schema = self.create_schema(client, enable_dynamic_field=False)[0]
schema.add_field("id_string", DataType.VARCHAR, max_length=64, is_primary=True, auto_id=False)
schema.add_field("embeddings", vector_type, dim=dim)
schema.add_field("title", DataType.VARCHAR, max_length=64, is_partition_key=True)
schema.add_field("nullable_field", DataType.INT64, nullable=nullable, default_value=10)
schema.add_field(
"array_field",
DataType.ARRAY,
element_type=DataType.INT64,
max_capacity=12,
max_length=64,
nullable=nullable,
)
index_params = self.prepare_index_params(client)[0]
index_params.add_index("embeddings", metric_type="COSINE")
# index_params.add_index("title")
self.create_collection(client, collection_name, dimension=dim, schema=schema, index_params=index_params)
collections = self.list_collections(client)[0]
assert collection_name in collections
check_items = {
"collection_name": collection_name,
"dim": dim,
"consistency_level": 0,
"enable_dynamic_field": False,
"num_partitions": 16,
"id_name": "id_string",
"vector_name": "embeddings",
}
if nullable:
check_items["nullable_fields"] = ["nullable_field", "array_field"]
if add_field:
self.add_collection_field(
client,
collection_name,
field_name="field_new_int64",
data_type=DataType.INT64,
nullable=True,
is_cluster_key=True,
)
self.add_collection_field(
client,
collection_name,
field_name="field_new_var",
data_type=DataType.VARCHAR,
nullable=True,
default_vaule="field_new_var",
max_length=64,
)
check_items["add_fields"] = ["field_new_int64", "field_new_var"]
self.describe_collection(
client, collection_name, check_task=CheckTasks.check_describe_collection_property, check_items=check_items
)
index = self.list_indexes(client, collection_name)[0]
assert index == ["embeddings"]
if self.has_collection(client, collection_name)[0]:
self.drop_collection(client, collection_name)
@pytest.mark.tags(CaseLabel.L2)
def test_milvus_client_collection_self_creation_multiple_vectors(self):
"""
target: test self create collection with multiple vectors
method: create collection with multiple vectors
expected: create collection with default schema, index, and load successfully
"""
client = self._client()
collection_name = cf.gen_unique_str(prefix)
dim = 128
# 1. create collection
schema = self.create_schema(client, enable_dynamic_field=False)[0]
schema.add_field("id_int64", DataType.INT64, is_primary=True, auto_id=False)
schema.add_field("embeddings", DataType.FLOAT_VECTOR, dim=dim)
schema.add_field("embeddings_1", DataType.INT8_VECTOR, dim=dim * 2)
schema.add_field("embeddings_2", DataType.FLOAT16_VECTOR, dim=int(dim / 2))
schema.add_field("embeddings_3", DataType.BFLOAT16_VECTOR, dim=int(dim / 2))
index_params = self.prepare_index_params(client)[0]
index_params.add_index("embeddings", metric_type="COSINE")
index_params.add_index("embeddings_1", metric_type="IP")
index_params.add_index("embeddings_2", metric_type="L2")
index_params.add_index("embeddings_3", metric_type="COSINE")
# index_params.add_index("title")
self.create_collection(client, collection_name, dimension=dim, schema=schema, index_params=index_params)
collections = self.list_collections(client)[0]
assert collection_name in collections
check_items = {
"collection_name": collection_name,
"dim": [dim, dim * 2, int(dim / 2), int(dim / 2)],
"consistency_level": 0,
"enable_dynamic_field": False,
"id_name": "id_int64",
"vector_name": ["embeddings", "embeddings_1", "embeddings_2", "embeddings_3"],
}
self.describe_collection(
client, collection_name, check_task=CheckTasks.check_describe_collection_property, check_items=check_items
)
index = self.list_indexes(client, collection_name)[0]
assert sorted(index) == sorted(["embeddings", "embeddings_1", "embeddings_2", "embeddings_3"])
if self.has_collection(client, collection_name)[0]:
self.drop_collection(client, collection_name)
@pytest.mark.tags(CaseLabel.L2)