pyseekdb 0.1.0.dev3__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- pyseekdb/__init__.py +90 -0
- pyseekdb/client/__init__.py +324 -0
- pyseekdb/client/admin_client.py +202 -0
- pyseekdb/client/base_connection.py +82 -0
- pyseekdb/client/client_base.py +1921 -0
- pyseekdb/client/client_oceanbase_server.py +258 -0
- pyseekdb/client/client_seekdb_embedded.py +324 -0
- pyseekdb/client/client_seekdb_server.py +226 -0
- pyseekdb/client/collection.py +485 -0
- pyseekdb/client/database.py +55 -0
- pyseekdb/client/filters.py +357 -0
- pyseekdb/client/meta_info.py +15 -0
- pyseekdb/client/query_result.py +122 -0
- pyseekdb/client/sql_utils.py +48 -0
- pyseekdb/examples/comprehensive_example.py +412 -0
- pyseekdb/examples/simple_example.py +113 -0
- pyseekdb/tests/__init__.py +0 -0
- pyseekdb/tests/test_admin_database_management.py +307 -0
- pyseekdb/tests/test_client_creation.py +425 -0
- pyseekdb/tests/test_collection_dml.py +652 -0
- pyseekdb/tests/test_collection_get.py +550 -0
- pyseekdb/tests/test_collection_hybrid_search.py +1126 -0
- pyseekdb/tests/test_collection_query.py +428 -0
- pyseekdb-0.1.0.dev3.dist-info/LICENSE +202 -0
- pyseekdb-0.1.0.dev3.dist-info/METADATA +856 -0
- pyseekdb-0.1.0.dev3.dist-info/RECORD +27 -0
- pyseekdb-0.1.0.dev3.dist-info/WHEEL +4 -0
|
@@ -0,0 +1,652 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Collection DML tests - testing collection.add(), collection.delete(), collection.upsert(), collection.update() interfaces for all three modes
|
|
3
|
+
Supports configuring connection parameters via environment variables
|
|
4
|
+
"""
|
|
5
|
+
import pytest
|
|
6
|
+
import sys
|
|
7
|
+
import os
|
|
8
|
+
import time
|
|
9
|
+
import uuid
|
|
10
|
+
from pathlib import Path
|
|
11
|
+
|
|
12
|
+
# Add project path
|
|
13
|
+
project_root = Path(__file__).parent.parent.parent
|
|
14
|
+
sys.path.insert(0, str(project_root))
|
|
15
|
+
|
|
16
|
+
import seekdbclient
|
|
17
|
+
from seekdbclient.client.meta_info import CollectionNames, CollectionFieldNames
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
# ==================== Environment Variable Configuration ====================
|
|
21
|
+
# Embedded mode
|
|
22
|
+
SEEKDB_PATH = os.environ.get('SEEKDB_PATH', os.path.join(project_root, "seekdb_store"))
|
|
23
|
+
SEEKDB_DATABASE = os.environ.get('SEEKDB_DATABASE', 'test')
|
|
24
|
+
|
|
25
|
+
# Server mode
|
|
26
|
+
SERVER_HOST = os.environ.get('SERVER_HOST', 'localhost')
|
|
27
|
+
SERVER_PORT = int(os.environ.get('SERVER_PORT', '2881'))
|
|
28
|
+
SERVER_DATABASE = os.environ.get('SERVER_DATABASE', 'test')
|
|
29
|
+
SERVER_USER = os.environ.get('SERVER_USER', 'root')
|
|
30
|
+
SERVER_PASSWORD = os.environ.get('SERVER_PASSWORD', '')
|
|
31
|
+
|
|
32
|
+
# OceanBase mode
|
|
33
|
+
OB_HOST = os.environ.get('OB_HOST', 'localhost')
|
|
34
|
+
OB_PORT = int(os.environ.get('OB_PORT', '11202'))
|
|
35
|
+
OB_TENANT = os.environ.get('OB_TENANT', 'mysql')
|
|
36
|
+
OB_DATABASE = os.environ.get('OB_DATABASE', 'test')
|
|
37
|
+
OB_USER = os.environ.get('OB_USER', 'root')
|
|
38
|
+
OB_PASSWORD = os.environ.get('OB_PASSWORD', '')
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
class TestCollectionDML:
|
|
42
|
+
"""Test collection DML operations (add, delete, upsert, update) for all three modes"""
|
|
43
|
+
|
|
44
|
+
def test_embedded_collection_dml(self):
|
|
45
|
+
"""Test collection DML operations with embedded client"""
|
|
46
|
+
if not os.path.exists(SEEKDB_PATH):
|
|
47
|
+
pytest.skip(
|
|
48
|
+
f"SeekDB data directory does not exist: {SEEKDB_PATH}\n"
|
|
49
|
+
f"Set SEEKDB_PATH environment variable to run this test"
|
|
50
|
+
)
|
|
51
|
+
|
|
52
|
+
# Check if seekdb package is available
|
|
53
|
+
try:
|
|
54
|
+
import seekdb
|
|
55
|
+
except ImportError:
|
|
56
|
+
pytest.skip("SeekDB embedded package is not installed")
|
|
57
|
+
|
|
58
|
+
# Create embedded client
|
|
59
|
+
client = seekdbclient.Client(
|
|
60
|
+
path=SEEKDB_PATH,
|
|
61
|
+
database=SEEKDB_DATABASE
|
|
62
|
+
)
|
|
63
|
+
|
|
64
|
+
assert client is not None
|
|
65
|
+
assert hasattr(client, '_server')
|
|
66
|
+
assert isinstance(client._server, seekdbclient.SeekdbEmbeddedClient)
|
|
67
|
+
|
|
68
|
+
# Create test collection using execute
|
|
69
|
+
collection_name = f"test_dml_{int(time.time())}"
|
|
70
|
+
table_name = CollectionNames.table_name(collection_name)
|
|
71
|
+
dimension = 3
|
|
72
|
+
|
|
73
|
+
# Create table using execute
|
|
74
|
+
create_table_sql = f"""CREATE TABLE `{table_name}` (
|
|
75
|
+
{CollectionFieldNames.ID} varbinary(512) PRIMARY KEY NOT NULL,
|
|
76
|
+
{CollectionFieldNames.DOCUMENT} string,
|
|
77
|
+
{CollectionFieldNames.EMBEDDING} vector({dimension}),
|
|
78
|
+
{CollectionFieldNames.METADATA} json,
|
|
79
|
+
FULLTEXT INDEX idx1({CollectionFieldNames.DOCUMENT}),
|
|
80
|
+
VECTOR INDEX idx2 ({CollectionFieldNames.EMBEDDING}) with(distance=l2, type=hnsw, lib=vsag)
|
|
81
|
+
) ORGANIZATION = HEAP;"""
|
|
82
|
+
client._server.execute(create_table_sql)
|
|
83
|
+
|
|
84
|
+
# Get collection object
|
|
85
|
+
collection = client.get_collection(name=collection_name)
|
|
86
|
+
|
|
87
|
+
try:
|
|
88
|
+
# Test 1: collection.add - Add single item
|
|
89
|
+
print(f"\n✅ Testing collection.add() - single item for embedded client")
|
|
90
|
+
test_id_1 = str(uuid.uuid4())
|
|
91
|
+
collection.add(
|
|
92
|
+
ids=test_id_1,
|
|
93
|
+
vectors=[1.0, 2.0, 3.0],
|
|
94
|
+
documents="This is test document 1",
|
|
95
|
+
metadatas={"category": "test", "score": 100}
|
|
96
|
+
)
|
|
97
|
+
|
|
98
|
+
# Verify using collection.get
|
|
99
|
+
results = collection.get(ids=test_id_1)
|
|
100
|
+
assert len(results) == 1
|
|
101
|
+
result_dict = results[0].to_dict() if hasattr(results[0], 'to_dict') else results[0]
|
|
102
|
+
assert result_dict.get(CollectionFieldNames.ID) == test_id_1
|
|
103
|
+
assert result_dict.get(CollectionFieldNames.DOCUMENT) == "This is test document 1"
|
|
104
|
+
assert result_dict.get(CollectionFieldNames.METADATA, {}).get('category') == "test"
|
|
105
|
+
print(f" Successfully added and verified item with ID: {test_id_1}")
|
|
106
|
+
|
|
107
|
+
# Test 2: collection.add - Add multiple items
|
|
108
|
+
print(f"✅ Testing collection.add() - multiple items")
|
|
109
|
+
test_ids = [str(uuid.uuid4()), str(uuid.uuid4()), str(uuid.uuid4())]
|
|
110
|
+
collection.add(
|
|
111
|
+
ids=test_ids,
|
|
112
|
+
vectors=[[2.0, 3.0, 4.0], [3.0, 4.0, 5.0], [4.0, 5.0, 6.0]],
|
|
113
|
+
documents=["Document 2", "Document 3", "Document 4"],
|
|
114
|
+
metadatas=[
|
|
115
|
+
{"category": "test", "score": 90},
|
|
116
|
+
{"category": "test", "score": 85},
|
|
117
|
+
{"category": "demo", "score": 80}
|
|
118
|
+
]
|
|
119
|
+
)
|
|
120
|
+
|
|
121
|
+
# Verify using collection.get
|
|
122
|
+
results = collection.get(ids=test_ids)
|
|
123
|
+
assert len(results) == 3
|
|
124
|
+
print(f" Successfully added and verified {len(results)} items")
|
|
125
|
+
|
|
126
|
+
# Test 3: collection.update - Update existing item
|
|
127
|
+
print(f"✅ Testing collection.update() - update existing item")
|
|
128
|
+
collection.update(
|
|
129
|
+
ids=test_id_1,
|
|
130
|
+
documents="Updated document 1",
|
|
131
|
+
metadatas={"category": "test", "score": 95, "updated": True}
|
|
132
|
+
)
|
|
133
|
+
|
|
134
|
+
# Verify update using collection.get
|
|
135
|
+
results = collection.get(ids=test_id_1)
|
|
136
|
+
assert len(results) == 1
|
|
137
|
+
result_dict = results[0].to_dict() if hasattr(results[0], 'to_dict') else results[0]
|
|
138
|
+
assert result_dict.get(CollectionFieldNames.DOCUMENT) == "Updated document 1"
|
|
139
|
+
assert result_dict.get(CollectionFieldNames.METADATA, {}).get('score') == 95
|
|
140
|
+
assert result_dict.get(CollectionFieldNames.METADATA, {}).get('updated') is True
|
|
141
|
+
print(f" Successfully updated and verified item with ID: {test_id_1}")
|
|
142
|
+
|
|
143
|
+
# Test 4: collection.upsert - Upsert existing item (should update)
|
|
144
|
+
print(f"✅ Testing collection.upsert() - upsert existing item (update)")
|
|
145
|
+
collection.upsert(
|
|
146
|
+
ids=test_id_1,
|
|
147
|
+
documents="Upserted document 1",
|
|
148
|
+
metadatas={"category": "test", "score": 98}
|
|
149
|
+
)
|
|
150
|
+
|
|
151
|
+
# Verify upsert using collection.get
|
|
152
|
+
results = collection.get(ids=test_id_1)
|
|
153
|
+
assert len(results) == 1
|
|
154
|
+
result_dict = results[0].to_dict() if hasattr(results[0], 'to_dict') else results[0]
|
|
155
|
+
assert result_dict.get(CollectionFieldNames.DOCUMENT) == "Upserted document 1"
|
|
156
|
+
assert result_dict.get(CollectionFieldNames.METADATA, {}).get('score') == 98
|
|
157
|
+
print(f" Successfully upserted (update) and verified item with ID: {test_id_1}")
|
|
158
|
+
|
|
159
|
+
# Test 5: collection.upsert - Upsert new item (should insert)
|
|
160
|
+
print(f"✅ Testing collection.upsert() - upsert new item (insert)")
|
|
161
|
+
test_id_new = str(uuid.uuid4())
|
|
162
|
+
collection.upsert(
|
|
163
|
+
ids=test_id_new,
|
|
164
|
+
vectors=[5.0, 6.0, 7.0],
|
|
165
|
+
documents="New upserted document",
|
|
166
|
+
metadatas={"category": "new", "score": 99}
|
|
167
|
+
)
|
|
168
|
+
|
|
169
|
+
# Verify upsert using collection.get
|
|
170
|
+
results = collection.get(ids=test_id_new)
|
|
171
|
+
assert len(results) == 1
|
|
172
|
+
result_dict = results[0].to_dict() if hasattr(results[0], 'to_dict') else results[0]
|
|
173
|
+
assert result_dict.get(CollectionFieldNames.DOCUMENT) == "New upserted document"
|
|
174
|
+
assert result_dict.get(CollectionFieldNames.METADATA, {}).get('category') == "new"
|
|
175
|
+
print(f" Successfully upserted (insert) and verified item with ID: {test_id_new}")
|
|
176
|
+
|
|
177
|
+
# Test 6: collection.delete - Delete by ID
|
|
178
|
+
print(f"✅ Testing collection.delete() - delete by ID")
|
|
179
|
+
# Delete one of the test items
|
|
180
|
+
collection.delete(ids=test_ids[0])
|
|
181
|
+
|
|
182
|
+
# Verify deletion using collection.get
|
|
183
|
+
results = collection.get(ids=test_ids[0])
|
|
184
|
+
assert len(results) == 0
|
|
185
|
+
print(f" Successfully deleted item with ID: {test_ids[0]}")
|
|
186
|
+
|
|
187
|
+
# Verify other items still exist
|
|
188
|
+
results = collection.get(ids=test_ids[1:])
|
|
189
|
+
assert len(results) == 2
|
|
190
|
+
print(f" Verified other items still exist")
|
|
191
|
+
|
|
192
|
+
# Test 7: collection.delete - Delete by metadata filter
|
|
193
|
+
print(f"✅ Testing collection.delete() - delete by metadata filter")
|
|
194
|
+
# Delete items with category="demo"
|
|
195
|
+
collection.delete(where={"category": {"$eq": "demo"}})
|
|
196
|
+
|
|
197
|
+
# Verify deletion using collection.get
|
|
198
|
+
results = collection.get(where={"category": {"$eq": "demo"}})
|
|
199
|
+
assert len(results) == 0
|
|
200
|
+
print(f" Successfully deleted items with category='demo'")
|
|
201
|
+
|
|
202
|
+
# Test 8: Verify final state using collection.get
|
|
203
|
+
print(f"✅ Testing final state verification")
|
|
204
|
+
all_results = collection.get(limit=100)
|
|
205
|
+
print(f" Final collection count: {len(all_results)} items")
|
|
206
|
+
assert len(all_results) > 0
|
|
207
|
+
|
|
208
|
+
finally:
|
|
209
|
+
# Cleanup
|
|
210
|
+
try:
|
|
211
|
+
client.delete_collection(name=collection_name)
|
|
212
|
+
print(f" Cleaned up collection: {collection_name}")
|
|
213
|
+
except Exception as cleanup_error:
|
|
214
|
+
print(f" Warning: Failed to cleanup collection: {cleanup_error}")
|
|
215
|
+
|
|
216
|
+
def test_server_collection_dml(self):
|
|
217
|
+
"""Test collection DML operations with server client"""
|
|
218
|
+
# Create server client
|
|
219
|
+
client = seekdbclient.Client(
|
|
220
|
+
host=SERVER_HOST,
|
|
221
|
+
port=SERVER_PORT,
|
|
222
|
+
database=SERVER_DATABASE,
|
|
223
|
+
user=SERVER_USER,
|
|
224
|
+
password=SERVER_PASSWORD
|
|
225
|
+
)
|
|
226
|
+
|
|
227
|
+
assert client is not None
|
|
228
|
+
assert hasattr(client, '_server')
|
|
229
|
+
assert isinstance(client._server, seekdbclient.SeekdbServerClient)
|
|
230
|
+
|
|
231
|
+
# Test connection
|
|
232
|
+
try:
|
|
233
|
+
result = client._server.execute("SELECT 1 as test")
|
|
234
|
+
assert result is not None
|
|
235
|
+
except Exception as e:
|
|
236
|
+
pytest.skip(f"Server connection failed ({SERVER_HOST}:{SERVER_PORT}): {e}")
|
|
237
|
+
|
|
238
|
+
# Create test collection using execute
|
|
239
|
+
collection_name = f"test_dml_{int(time.time())}"
|
|
240
|
+
table_name = CollectionNames.table_name(collection_name)
|
|
241
|
+
dimension = 3
|
|
242
|
+
|
|
243
|
+
# Create table using execute
|
|
244
|
+
create_table_sql = f"""CREATE TABLE `{table_name}` (
|
|
245
|
+
{CollectionFieldNames.ID} varbinary(512) PRIMARY KEY NOT NULL,
|
|
246
|
+
{CollectionFieldNames.DOCUMENT} string,
|
|
247
|
+
{CollectionFieldNames.EMBEDDING} vector({dimension}),
|
|
248
|
+
{CollectionFieldNames.METADATA} json,
|
|
249
|
+
FULLTEXT INDEX idx1({CollectionFieldNames.DOCUMENT}),
|
|
250
|
+
VECTOR INDEX idx2 ({CollectionFieldNames.EMBEDDING}) with(distance=l2, type=hnsw, lib=vsag)
|
|
251
|
+
) ORGANIZATION = HEAP;"""
|
|
252
|
+
client._server.execute(create_table_sql)
|
|
253
|
+
|
|
254
|
+
# Get collection object
|
|
255
|
+
collection = client.get_collection(name=collection_name)
|
|
256
|
+
|
|
257
|
+
try:
|
|
258
|
+
# Test 1: collection.add - Add single item
|
|
259
|
+
print(f"\n✅ Testing collection.add() - single item for server client")
|
|
260
|
+
test_id_1 = str(uuid.uuid4())
|
|
261
|
+
collection.add(
|
|
262
|
+
ids=test_id_1,
|
|
263
|
+
vectors=[1.0, 2.0, 3.0],
|
|
264
|
+
documents="This is test document 1",
|
|
265
|
+
metadatas={"category": "test", "score": 100}
|
|
266
|
+
)
|
|
267
|
+
|
|
268
|
+
# Verify using collection.get
|
|
269
|
+
results = collection.get(ids=test_id_1)
|
|
270
|
+
assert len(results) == 1
|
|
271
|
+
result_dict = results[0].to_dict() if hasattr(results[0], 'to_dict') else results[0]
|
|
272
|
+
assert result_dict.get(CollectionFieldNames.ID) == test_id_1
|
|
273
|
+
assert result_dict.get(CollectionFieldNames.DOCUMENT) == "This is test document 1"
|
|
274
|
+
assert result_dict.get(CollectionFieldNames.METADATA, {}).get('category') == "test"
|
|
275
|
+
print(f" Successfully added and verified item with ID: {test_id_1}")
|
|
276
|
+
|
|
277
|
+
# Test 2: collection.add - Add multiple items
|
|
278
|
+
print(f"✅ Testing collection.add() - multiple items")
|
|
279
|
+
test_ids = [str(uuid.uuid4()), str(uuid.uuid4()), str(uuid.uuid4())]
|
|
280
|
+
collection.add(
|
|
281
|
+
ids=test_ids,
|
|
282
|
+
vectors=[[2.0, 3.0, 4.0], [3.0, 4.0, 5.0], [4.0, 5.0, 6.0]],
|
|
283
|
+
documents=["Document 2", "Document 3", "Document 4"],
|
|
284
|
+
metadatas=[
|
|
285
|
+
{"category": "test", "score": 90},
|
|
286
|
+
{"category": "test", "score": 85},
|
|
287
|
+
{"category": "demo", "score": 80}
|
|
288
|
+
]
|
|
289
|
+
)
|
|
290
|
+
|
|
291
|
+
# Verify using collection.get
|
|
292
|
+
results = collection.get(ids=test_ids)
|
|
293
|
+
assert len(results) == 3
|
|
294
|
+
print(f" Successfully added and verified {len(results)} items")
|
|
295
|
+
|
|
296
|
+
# Test 3: collection.update - Update existing item
|
|
297
|
+
print(f"✅ Testing collection.update() - update existing item")
|
|
298
|
+
collection.update(
|
|
299
|
+
ids=test_id_1,
|
|
300
|
+
documents="Updated document 1",
|
|
301
|
+
metadatas={"category": "test", "score": 95, "updated": True}
|
|
302
|
+
)
|
|
303
|
+
|
|
304
|
+
# Verify update using collection.get
|
|
305
|
+
results = collection.get(ids=test_id_1)
|
|
306
|
+
assert len(results) == 1
|
|
307
|
+
result_dict = results[0].to_dict() if hasattr(results[0], 'to_dict') else results[0]
|
|
308
|
+
assert result_dict.get(CollectionFieldNames.DOCUMENT) == "Updated document 1"
|
|
309
|
+
assert result_dict.get(CollectionFieldNames.METADATA, {}).get('score') == 95
|
|
310
|
+
assert result_dict.get(CollectionFieldNames.METADATA, {}).get('updated') is True
|
|
311
|
+
print(f" Successfully updated and verified item with ID: {test_id_1}")
|
|
312
|
+
|
|
313
|
+
# Test 4: collection.update - Update multiple items
|
|
314
|
+
print(f"✅ Testing collection.update() - update multiple items")
|
|
315
|
+
collection.update(
|
|
316
|
+
ids=test_ids[:2],
|
|
317
|
+
vectors=[[2.1, 3.1, 4.1], [3.1, 4.1, 5.1]],
|
|
318
|
+
metadatas=[
|
|
319
|
+
{"category": "test", "score": 92},
|
|
320
|
+
{"category": "test", "score": 87}
|
|
321
|
+
]
|
|
322
|
+
)
|
|
323
|
+
|
|
324
|
+
# Verify update using collection.get
|
|
325
|
+
results = collection.get(ids=test_ids[:2])
|
|
326
|
+
assert len(results) == 2
|
|
327
|
+
print(f" Successfully updated and verified {len(results)} items")
|
|
328
|
+
|
|
329
|
+
# Test 5: collection.upsert - Upsert existing item (should update)
|
|
330
|
+
print(f"✅ Testing collection.upsert() - upsert existing item (update)")
|
|
331
|
+
collection.upsert(
|
|
332
|
+
ids=test_id_1,
|
|
333
|
+
documents="Upserted document 1",
|
|
334
|
+
metadatas={"category": "test", "score": 98}
|
|
335
|
+
)
|
|
336
|
+
|
|
337
|
+
# Verify upsert using collection.get
|
|
338
|
+
results = collection.get(ids=test_id_1)
|
|
339
|
+
assert len(results) == 1
|
|
340
|
+
result_dict = results[0].to_dict() if hasattr(results[0], 'to_dict') else results[0]
|
|
341
|
+
assert result_dict.get(CollectionFieldNames.DOCUMENT) == "Upserted document 1"
|
|
342
|
+
assert result_dict.get(CollectionFieldNames.METADATA, {}).get('score') == 98
|
|
343
|
+
print(f" Successfully upserted (update) and verified item with ID: {test_id_1}")
|
|
344
|
+
|
|
345
|
+
# Test 6: collection.upsert - Upsert new item (should insert)
|
|
346
|
+
print(f"✅ Testing collection.upsert() - upsert new item (insert)")
|
|
347
|
+
test_id_new = str(uuid.uuid4())
|
|
348
|
+
collection.upsert(
|
|
349
|
+
ids=test_id_new,
|
|
350
|
+
vectors=[5.0, 6.0, 7.0],
|
|
351
|
+
documents="New upserted document",
|
|
352
|
+
metadatas={"category": "new", "score": 99}
|
|
353
|
+
)
|
|
354
|
+
|
|
355
|
+
# Verify upsert using collection.get
|
|
356
|
+
results = collection.get(ids=test_id_new)
|
|
357
|
+
assert len(results) == 1
|
|
358
|
+
result_dict = results[0].to_dict() if hasattr(results[0], 'to_dict') else results[0]
|
|
359
|
+
assert result_dict.get(CollectionFieldNames.DOCUMENT) == "New upserted document"
|
|
360
|
+
assert result_dict.get(CollectionFieldNames.METADATA, {}).get('category') == "new"
|
|
361
|
+
print(f" Successfully upserted (insert) and verified item with ID: {test_id_new}")
|
|
362
|
+
|
|
363
|
+
# Test 7: collection.delete - Delete by ID
|
|
364
|
+
print(f"✅ Testing collection.delete() - delete by ID")
|
|
365
|
+
# Delete one of the test items
|
|
366
|
+
collection.delete(ids=test_ids[0])
|
|
367
|
+
|
|
368
|
+
# Verify deletion using collection.get
|
|
369
|
+
results = collection.get(ids=test_ids[0])
|
|
370
|
+
assert len(results) == 0
|
|
371
|
+
print(f" Successfully deleted item with ID: {test_ids[0]}")
|
|
372
|
+
|
|
373
|
+
# Verify other items still exist
|
|
374
|
+
results = collection.get(ids=test_ids[1:])
|
|
375
|
+
assert len(results) == 2
|
|
376
|
+
print(f" Verified other items still exist")
|
|
377
|
+
|
|
378
|
+
# Test 8: collection.delete - Delete by metadata filter
|
|
379
|
+
print(f"✅ Testing collection.delete() - delete by metadata filter")
|
|
380
|
+
# Delete items with category="demo"
|
|
381
|
+
collection.delete(where={"category": {"$eq": "demo"}})
|
|
382
|
+
|
|
383
|
+
# Verify deletion using collection.get
|
|
384
|
+
results = collection.get(where={"category": {"$eq": "demo"}})
|
|
385
|
+
assert len(results) == 0
|
|
386
|
+
print(f" Successfully deleted items with category='demo'")
|
|
387
|
+
|
|
388
|
+
# Test 9: collection.delete - Delete by document filter
|
|
389
|
+
print(f"✅ Testing collection.delete() - delete by document filter")
|
|
390
|
+
# Add an item with specific document content
|
|
391
|
+
test_id_doc = str(uuid.uuid4())
|
|
392
|
+
collection.add(
|
|
393
|
+
ids=test_id_doc,
|
|
394
|
+
vectors=[6.0, 7.0, 8.0],
|
|
395
|
+
documents="Delete this document",
|
|
396
|
+
metadatas={"category": "temp"}
|
|
397
|
+
)
|
|
398
|
+
|
|
399
|
+
# Delete by document filter
|
|
400
|
+
collection.delete(where_document={"$contains": "Delete this"})
|
|
401
|
+
|
|
402
|
+
# Verify deletion using collection.get
|
|
403
|
+
results = collection.get(where_document={"$contains": "Delete this"})
|
|
404
|
+
assert len(results) == 0
|
|
405
|
+
print(f" Successfully deleted items by document filter")
|
|
406
|
+
|
|
407
|
+
# Test 10: Verify final state using collection.get
|
|
408
|
+
print(f"✅ Testing final state verification")
|
|
409
|
+
all_results = collection.get(limit=100)
|
|
410
|
+
print(f" Final collection count: {len(all_results)} items")
|
|
411
|
+
assert len(all_results) > 0
|
|
412
|
+
|
|
413
|
+
finally:
|
|
414
|
+
# Cleanup
|
|
415
|
+
try:
|
|
416
|
+
client.delete_collection(name=collection_name)
|
|
417
|
+
print(f" Cleaned up collection: {collection_name}")
|
|
418
|
+
except Exception as cleanup_error:
|
|
419
|
+
print(f" Warning: Failed to cleanup collection: {cleanup_error}")
|
|
420
|
+
|
|
421
|
+
def test_oceanbase_collection_dml(self):
|
|
422
|
+
"""Test collection DML operations with OceanBase client"""
|
|
423
|
+
# Create OceanBase client
|
|
424
|
+
client = seekdbclient.OBClient(
|
|
425
|
+
host=OB_HOST,
|
|
426
|
+
port=OB_PORT,
|
|
427
|
+
tenant=OB_TENANT,
|
|
428
|
+
database=OB_DATABASE,
|
|
429
|
+
user=OB_USER,
|
|
430
|
+
password=OB_PASSWORD
|
|
431
|
+
)
|
|
432
|
+
|
|
433
|
+
assert client is not None
|
|
434
|
+
assert hasattr(client, '_server')
|
|
435
|
+
assert isinstance(client._server, seekdbclient.OceanBaseServerClient)
|
|
436
|
+
|
|
437
|
+
# Test connection
|
|
438
|
+
try:
|
|
439
|
+
result = client._server.execute("SELECT 1 as test")
|
|
440
|
+
assert result is not None
|
|
441
|
+
except Exception as e:
|
|
442
|
+
pytest.skip(f"OceanBase connection failed ({OB_HOST}:{OB_PORT}): {e}")
|
|
443
|
+
|
|
444
|
+
# Create test collection using execute
|
|
445
|
+
collection_name = f"test_dml_{int(time.time())}"
|
|
446
|
+
table_name = CollectionNames.table_name(collection_name)
|
|
447
|
+
dimension = 3
|
|
448
|
+
|
|
449
|
+
# Create table using execute
|
|
450
|
+
create_table_sql = f"""CREATE TABLE `{table_name}` (
|
|
451
|
+
{CollectionFieldNames.ID} varbinary(512) PRIMARY KEY NOT NULL,
|
|
452
|
+
{CollectionFieldNames.DOCUMENT} string,
|
|
453
|
+
{CollectionFieldNames.EMBEDDING} vector({dimension}),
|
|
454
|
+
{CollectionFieldNames.METADATA} json,
|
|
455
|
+
FULLTEXT INDEX idx1({CollectionFieldNames.DOCUMENT}),
|
|
456
|
+
VECTOR INDEX idx2 ({CollectionFieldNames.EMBEDDING}) with(distance=l2, type=hnsw, lib=vsag)
|
|
457
|
+
) ORGANIZATION = HEAP;"""
|
|
458
|
+
client._server.execute(create_table_sql)
|
|
459
|
+
|
|
460
|
+
# Get collection object
|
|
461
|
+
collection = client.get_collection(name=collection_name)
|
|
462
|
+
|
|
463
|
+
try:
|
|
464
|
+
# Test 1: collection.add - Add single item
|
|
465
|
+
print(f"\n✅ Testing collection.add() - single item for OceanBase client")
|
|
466
|
+
test_id_1 = str(uuid.uuid4())
|
|
467
|
+
collection.add(
|
|
468
|
+
ids=test_id_1,
|
|
469
|
+
vectors=[1.0, 2.0, 3.0],
|
|
470
|
+
documents="This is test document 1",
|
|
471
|
+
metadatas={"category": "test", "score": 100}
|
|
472
|
+
)
|
|
473
|
+
|
|
474
|
+
# Verify using collection.get
|
|
475
|
+
results = collection.get(ids=test_id_1)
|
|
476
|
+
assert len(results) == 1
|
|
477
|
+
result_dict = results[0].to_dict() if hasattr(results[0], 'to_dict') else results[0]
|
|
478
|
+
assert result_dict.get(CollectionFieldNames.ID) == test_id_1
|
|
479
|
+
assert result_dict.get(CollectionFieldNames.DOCUMENT) == "This is test document 1"
|
|
480
|
+
assert result_dict.get(CollectionFieldNames.METADATA, {}).get('category') == "test"
|
|
481
|
+
print(f" Successfully added and verified item with ID: {test_id_1}")
|
|
482
|
+
|
|
483
|
+
# Test 2: collection.add - Add multiple items
|
|
484
|
+
print(f"✅ Testing collection.add() - multiple items")
|
|
485
|
+
test_ids = [str(uuid.uuid4()), str(uuid.uuid4()), str(uuid.uuid4())]
|
|
486
|
+
collection.add(
|
|
487
|
+
ids=test_ids,
|
|
488
|
+
vectors=[[2.0, 3.0, 4.0], [3.0, 4.0, 5.0], [4.0, 5.0, 6.0]],
|
|
489
|
+
documents=["Document 2", "Document 3", "Document 4"],
|
|
490
|
+
metadatas=[
|
|
491
|
+
{"category": "test", "score": 90},
|
|
492
|
+
{"category": "test", "score": 85},
|
|
493
|
+
{"category": "demo", "score": 80}
|
|
494
|
+
]
|
|
495
|
+
)
|
|
496
|
+
|
|
497
|
+
# Verify using collection.get
|
|
498
|
+
results = collection.get(ids=test_ids)
|
|
499
|
+
assert len(results) == 3
|
|
500
|
+
print(f" Successfully added and verified {len(results)} items")
|
|
501
|
+
|
|
502
|
+
# Test 3: collection.update - Update existing item
|
|
503
|
+
print(f"✅ Testing collection.update() - update existing item")
|
|
504
|
+
collection.update(
|
|
505
|
+
ids=test_id_1,
|
|
506
|
+
documents="Updated document 1",
|
|
507
|
+
metadatas={"category": "test", "score": 95, "updated": True}
|
|
508
|
+
)
|
|
509
|
+
|
|
510
|
+
# Verify update using collection.get
|
|
511
|
+
results = collection.get(ids=test_id_1)
|
|
512
|
+
assert len(results) == 1
|
|
513
|
+
result_dict = results[0].to_dict() if hasattr(results[0], 'to_dict') else results[0]
|
|
514
|
+
assert result_dict.get(CollectionFieldNames.DOCUMENT) == "Updated document 1"
|
|
515
|
+
assert result_dict.get(CollectionFieldNames.METADATA, {}).get('score') == 95
|
|
516
|
+
assert result_dict.get(CollectionFieldNames.METADATA, {}).get('updated') is True
|
|
517
|
+
print(f" Successfully updated and verified item with ID: {test_id_1}")
|
|
518
|
+
|
|
519
|
+
# Test 4: collection.update - Update multiple items with vectors
|
|
520
|
+
print(f"✅ Testing collection.update() - update multiple items with vectors")
|
|
521
|
+
collection.update(
|
|
522
|
+
ids=test_ids[:2],
|
|
523
|
+
vectors=[[2.1, 3.1, 4.1], [3.1, 4.1, 5.1]],
|
|
524
|
+
metadatas=[
|
|
525
|
+
{"category": "test", "score": 92},
|
|
526
|
+
{"category": "test", "score": 87}
|
|
527
|
+
]
|
|
528
|
+
)
|
|
529
|
+
|
|
530
|
+
# Verify update using collection.get
|
|
531
|
+
results = collection.get(ids=test_ids[:2], include=["embeddings"])
|
|
532
|
+
assert len(results) == 2
|
|
533
|
+
print(f" Successfully updated and verified {len(results)} items with vectors")
|
|
534
|
+
|
|
535
|
+
# Test 5: collection.upsert - Upsert existing item (should update)
|
|
536
|
+
print(f"✅ Testing collection.upsert() - upsert existing item (update)")
|
|
537
|
+
collection.upsert(
|
|
538
|
+
ids=test_id_1,
|
|
539
|
+
documents="Upserted document 1",
|
|
540
|
+
metadatas={"category": "test", "score": 98}
|
|
541
|
+
)
|
|
542
|
+
|
|
543
|
+
# Verify upsert using collection.get
|
|
544
|
+
results = collection.get(ids=test_id_1)
|
|
545
|
+
assert len(results) == 1
|
|
546
|
+
result_dict = results[0].to_dict() if hasattr(results[0], 'to_dict') else results[0]
|
|
547
|
+
assert result_dict.get(CollectionFieldNames.DOCUMENT) == "Upserted document 1"
|
|
548
|
+
assert result_dict.get(CollectionFieldNames.METADATA, {}).get('score') == 98
|
|
549
|
+
print(f" Successfully upserted (update) and verified item with ID: {test_id_1}")
|
|
550
|
+
|
|
551
|
+
# Test 6: collection.upsert - Upsert new item (should insert)
|
|
552
|
+
print(f"✅ Testing collection.upsert() - upsert new item (insert)")
|
|
553
|
+
test_id_new = str(uuid.uuid4())
|
|
554
|
+
collection.upsert(
|
|
555
|
+
ids=test_id_new,
|
|
556
|
+
vectors=[5.0, 6.0, 7.0],
|
|
557
|
+
documents="New upserted document",
|
|
558
|
+
metadatas={"category": "new", "score": 99}
|
|
559
|
+
)
|
|
560
|
+
|
|
561
|
+
# Verify upsert using collection.get
|
|
562
|
+
results = collection.get(ids=test_id_new)
|
|
563
|
+
assert len(results) == 1
|
|
564
|
+
result_dict = results[0].to_dict() if hasattr(results[0], 'to_dict') else results[0]
|
|
565
|
+
assert result_dict.get(CollectionFieldNames.DOCUMENT) == "New upserted document"
|
|
566
|
+
assert result_dict.get(CollectionFieldNames.METADATA, {}).get('category') == "new"
|
|
567
|
+
print(f" Successfully upserted (insert) and verified item with ID: {test_id_new}")
|
|
568
|
+
|
|
569
|
+
# Test 7: collection.delete - Delete by ID
|
|
570
|
+
print(f"✅ Testing collection.delete() - delete by ID")
|
|
571
|
+
# Delete one of the test items
|
|
572
|
+
collection.delete(ids=test_ids[0])
|
|
573
|
+
|
|
574
|
+
# Verify deletion using collection.get
|
|
575
|
+
results = collection.get(ids=test_ids[0])
|
|
576
|
+
assert len(results) == 0
|
|
577
|
+
print(f" Successfully deleted item with ID: {test_ids[0]}")
|
|
578
|
+
|
|
579
|
+
# Verify other items still exist
|
|
580
|
+
results = collection.get(ids=test_ids[1:])
|
|
581
|
+
assert len(results) == 2
|
|
582
|
+
print(f" Verified other items still exist")
|
|
583
|
+
|
|
584
|
+
# Test 8: collection.delete - Delete by metadata filter with comparison operator
|
|
585
|
+
print(f"✅ Testing collection.delete() - delete by metadata filter with $gte")
|
|
586
|
+
# Add items with different scores
|
|
587
|
+
test_id_score = str(uuid.uuid4())
|
|
588
|
+
collection.add(
|
|
589
|
+
ids=test_id_score,
|
|
590
|
+
vectors=[7.0, 8.0, 9.0],
|
|
591
|
+
documents="High score document",
|
|
592
|
+
metadatas={"category": "test", "score": 99}
|
|
593
|
+
)
|
|
594
|
+
|
|
595
|
+
# Delete items with score >= 99
|
|
596
|
+
collection.delete(where={"score": {"$gte": 99}})
|
|
597
|
+
|
|
598
|
+
# Verify deletion using collection.get
|
|
599
|
+
results = collection.get(where={"score": {"$gte": 99}})
|
|
600
|
+
assert len(results) == 0
|
|
601
|
+
print(f" Successfully deleted items with score >= 99")
|
|
602
|
+
|
|
603
|
+
# Test 9: collection.delete - Delete by document filter
|
|
604
|
+
print(f"✅ Testing collection.delete() - delete by document filter")
|
|
605
|
+
# Add an item with specific document content
|
|
606
|
+
test_id_doc = str(uuid.uuid4())
|
|
607
|
+
collection.add(
|
|
608
|
+
ids=test_id_doc,
|
|
609
|
+
vectors=[6.0, 7.0, 8.0],
|
|
610
|
+
documents="Delete this document",
|
|
611
|
+
metadatas={"category": "temp"}
|
|
612
|
+
)
|
|
613
|
+
|
|
614
|
+
# Delete by document filter
|
|
615
|
+
collection.delete(where_document={"$contains": "Delete this"})
|
|
616
|
+
|
|
617
|
+
# Verify deletion using collection.get
|
|
618
|
+
results = collection.get(where_document={"$contains": "Delete this"})
|
|
619
|
+
assert len(results) == 0
|
|
620
|
+
print(f" Successfully deleted items by document filter")
|
|
621
|
+
|
|
622
|
+
# Test 10: Verify final state using collection.get with filters
|
|
623
|
+
print(f"✅ Testing final state verification")
|
|
624
|
+
all_results = collection.get(limit=100)
|
|
625
|
+
print(f" Final collection count: {len(all_results)} items")
|
|
626
|
+
assert len(all_results) > 0
|
|
627
|
+
|
|
628
|
+
# Verify by category filter
|
|
629
|
+
test_results = collection.get(where={"category": {"$eq": "test"}})
|
|
630
|
+
print(f" Items with category='test': {len(test_results)}")
|
|
631
|
+
|
|
632
|
+
finally:
|
|
633
|
+
# Cleanup
|
|
634
|
+
try:
|
|
635
|
+
client.delete_collection(name=collection_name)
|
|
636
|
+
print(f" Cleaned up collection: {collection_name}")
|
|
637
|
+
except Exception as cleanup_error:
|
|
638
|
+
print(f" Warning: Failed to cleanup collection: {cleanup_error}")
|
|
639
|
+
|
|
640
|
+
|
|
641
|
+
if __name__ == "__main__":
|
|
642
|
+
print("\n" + "="*60)
|
|
643
|
+
print("SeekDBClient - Collection DML Tests")
|
|
644
|
+
print("="*60)
|
|
645
|
+
print(f"\nEnvironment Variable Configuration:")
|
|
646
|
+
print(f" Embedded mode: path={SEEKDB_PATH}, database={SEEKDB_DATABASE}")
|
|
647
|
+
print(f" Server mode: {SERVER_USER}@{SERVER_HOST}:{SERVER_PORT}/{SERVER_DATABASE}")
|
|
648
|
+
print(f" OceanBase mode: {OB_USER}@{OB_TENANT} -> {OB_HOST}:{OB_PORT}/{OB_DATABASE}")
|
|
649
|
+
print("="*60 + "\n")
|
|
650
|
+
|
|
651
|
+
pytest.main([__file__, "-v", "-s"])
|
|
652
|
+
|