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.
@@ -0,0 +1,550 @@
1
+ """
2
+ Collection get tests - testing collection.get() interface 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 json
10
+ import uuid
11
+ from pathlib import Path
12
+
13
+ # Add project path
14
+ project_root = Path(__file__).parent.parent.parent
15
+ sys.path.insert(0, str(project_root))
16
+
17
+ import seekdbclient
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 TestCollectionGet:
42
+ """Test collection.get() interface for all three modes"""
43
+
44
+ def _insert_test_data(self, client, collection_name: str):
45
+ """Helper method to insert test data and return inserted IDs"""
46
+ table_name = f"c$v1${collection_name}"
47
+
48
+ # Insert test data with vectors, documents, and metadata
49
+ test_data = [
50
+ {
51
+ "_id": str(uuid.uuid4()),
52
+ "document": "This is a test document about machine learning",
53
+ "embedding": [1.0, 2.0, 3.0],
54
+ "metadata": {"category": "AI", "score": 95, "tag": "ml"}
55
+ },
56
+ {
57
+ "_id": str(uuid.uuid4()),
58
+ "document": "Python programming tutorial for beginners",
59
+ "embedding": [2.0, 3.0, 4.0],
60
+ "metadata": {"category": "Programming", "score": 88, "tag": "python"}
61
+ },
62
+ {
63
+ "_id": str(uuid.uuid4()),
64
+ "document": "Advanced machine learning algorithms",
65
+ "embedding": [1.1, 2.1, 3.1],
66
+ "metadata": {"category": "AI", "score": 92, "tag": "ml"}
67
+ },
68
+ {
69
+ "_id": str(uuid.uuid4()),
70
+ "document": "Data science with Python",
71
+ "embedding": [2.1, 3.1, 4.1],
72
+ "metadata": {"category": "Data Science", "score": 90, "tag": "python"}
73
+ },
74
+ {
75
+ "_id": str(uuid.uuid4()),
76
+ "document": "Introduction to neural networks",
77
+ "embedding": [1.2, 2.2, 3.2],
78
+ "metadata": {"category": "AI", "score": 85, "tag": "neural"}
79
+ }
80
+ ]
81
+
82
+ # Store inserted IDs for return (using generated UUIDs)
83
+ inserted_ids = []
84
+
85
+ # Insert all data with generated UUIDs
86
+ for data in test_data:
87
+ # Use string ID directly (support any string format)
88
+ id_str = data["_id"]
89
+ inserted_ids.append(id_str) # Store original ID string for return
90
+
91
+ # Escape single quotes in ID
92
+ id_str_escaped = id_str.replace("'", "''")
93
+
94
+ # Convert vector to string format: [1.0,2.0,3.0]
95
+ vector_str = "[" + ",".join(map(str, data["embedding"])) + "]"
96
+ # Convert metadata to JSON string
97
+ metadata_str = json.dumps(data["metadata"]).replace("'", "\\'")
98
+ # Escape single quotes in document
99
+ document_str = data["document"].replace("'", "\\'")
100
+
101
+ # Use CAST to convert string to binary for varbinary(512) field
102
+ sql = f"""INSERT INTO `{table_name}` (_id, document, embedding, metadata)
103
+ VALUES (CAST('{id_str_escaped}' AS BINARY), '{document_str}', '{vector_str}', '{metadata_str}')"""
104
+ client._server.execute(sql)
105
+
106
+ return inserted_ids
107
+
108
+ def test_embedded_collection_get(self):
109
+ """Test collection.get() with embedded client"""
110
+ if not os.path.exists(SEEKDB_PATH):
111
+ pytest.skip(
112
+ f"SeekDB data directory does not exist: {SEEKDB_PATH}\n"
113
+ f"Set SEEKDB_PATH environment variable to run this test"
114
+ )
115
+
116
+ # Check if seekdb package is available
117
+ try:
118
+ import seekdb
119
+ except ImportError:
120
+ pytest.skip("SeekDB embedded package is not installed")
121
+
122
+ # Create embedded client
123
+ client = seekdbclient.Client(
124
+ path=SEEKDB_PATH,
125
+ database=SEEKDB_DATABASE
126
+ )
127
+
128
+ assert client is not None
129
+ assert hasattr(client, '_server')
130
+ assert isinstance(client._server, seekdbclient.SeekdbEmbeddedClient)
131
+
132
+ # Create test collection
133
+ collection_name = f"test_get_{int(time.time())}"
134
+ collection = client.create_collection(name=collection_name, dimension=3)
135
+
136
+ try:
137
+ # Insert test data and get IDs
138
+ inserted_ids = self._insert_test_data(client, collection_name)
139
+ assert len(inserted_ids) > 0, f"Failed to get inserted IDs. Expected at least 1, got {len(inserted_ids)}"
140
+ if len(inserted_ids) < 5:
141
+ print(f" Warning: Expected 5 inserted IDs, but got {len(inserted_ids)}")
142
+
143
+ # Test 1: Get by single ID
144
+ print(f"\n✅ Testing get by single ID for embedded client")
145
+ results = collection.get(ids=inserted_ids[0])
146
+ assert results is not None
147
+ assert len(results) == 1
148
+ print(f" Found {len(results)} result for ID={inserted_ids[0]}")
149
+
150
+ # Test 2: Get by multiple IDs
151
+ print(f"✅ Testing get by multiple IDs")
152
+ if len(inserted_ids) >= 2:
153
+ results = collection.get(ids=inserted_ids[:2])
154
+ assert results is not None
155
+ assert len(results) <= 2
156
+ print(f" Found {len(results)} results for IDs={inserted_ids[:2]}")
157
+
158
+ # Test 3: Get by metadata filter
159
+ print(f"✅ Testing get with metadata filter")
160
+ results = collection.get(
161
+ where={"category": {"$eq": "AI"}},
162
+ limit=10
163
+ )
164
+ assert results is not None
165
+ assert len(results) > 0
166
+ print(f" Found {len(results)} results with category='AI'")
167
+
168
+ # Test 4: Get by document filter
169
+ print(f"✅ Testing get with document filter")
170
+ results = collection.get(
171
+ where_document={"$contains": "machine learning"},
172
+ limit=10
173
+ )
174
+ assert results is not None
175
+ print(f" Found {len(results)} results containing 'machine learning'")
176
+
177
+ # Test 5: Get with include parameter
178
+ print(f"✅ Testing get with include parameter")
179
+ results = collection.get(
180
+ ids=inserted_ids[:2],
181
+ include=["documents", "metadatas"]
182
+ )
183
+ assert results is not None
184
+ assert isinstance(results, list), "Multiple IDs should return List[QueryResult]"
185
+ assert len(results) == 2, f"Expected 2 QueryResult objects, got {len(results)}"
186
+ for i, result in enumerate(results):
187
+ assert isinstance(result, seekdbclient.QueryResult), f"Result {i} should be QueryResult"
188
+ if len(result) > 0:
189
+ for item in result:
190
+ result_dict = item.to_dict() if hasattr(item, 'to_dict') else item
191
+ assert '_id' in result_dict
192
+ print(f" QueryResult {i} keys: {list(result[0].to_dict().keys()) if len(result) > 0 else 'empty'}")
193
+
194
+ # Test 6: Get all data with limit
195
+ print(f"✅ Testing get all data with limit")
196
+ results = collection.get(limit=3)
197
+ assert results is not None
198
+ assert len(results) <= 3
199
+ print(f" Found {len(results)} results (limit=3)")
200
+
201
+ # Test 7: Get by multiple IDs (should return List[QueryResult])
202
+ print(f"✅ Testing get by multiple IDs (returns List[QueryResult])")
203
+ if len(inserted_ids) >= 3:
204
+ results = collection.get(ids=inserted_ids[:3])
205
+ assert results is not None
206
+ assert isinstance(results, list), "Multiple IDs should return List[QueryResult]"
207
+ assert len(results) == 3, f"Expected 3 QueryResult objects, got {len(results)}"
208
+ for i, result in enumerate(results):
209
+ assert isinstance(result, seekdbclient.QueryResult), f"Result {i} should be QueryResult"
210
+ assert len(result) >= 0, f"QueryResult {i} should exist (may be empty if ID not found)"
211
+ print(f" QueryResult {i} for ID {inserted_ids[i]}: {len(result)} items")
212
+
213
+ # Test 8: Single ID still returns single QueryResult (backward compatibility)
214
+ print(f"✅ Testing single ID returns single QueryResult (backward compatibility)")
215
+ results = collection.get(ids=inserted_ids[0])
216
+ assert results is not None
217
+ assert isinstance(results, seekdbclient.QueryResult), "Single ID should return QueryResult, not list"
218
+ assert len(results) == 1
219
+ print(f" Single QueryResult with {len(results)} item")
220
+
221
+ finally:
222
+ # Cleanup
223
+ try:
224
+ client.delete_collection(name=collection_name)
225
+ print(f" Cleaned up collection: {collection_name}")
226
+ except Exception as cleanup_error:
227
+ print(f" Warning: Failed to cleanup collection: {cleanup_error}")
228
+
229
+ def test_server_collection_get(self):
230
+ """Test collection.get() with server client"""
231
+ # Create server client
232
+ client = seekdbclient.Client(
233
+ host=SERVER_HOST,
234
+ port=SERVER_PORT,
235
+ database=SERVER_DATABASE,
236
+ user=SERVER_USER,
237
+ password=SERVER_PASSWORD
238
+ )
239
+
240
+ assert client is not None
241
+ assert hasattr(client, '_server')
242
+ assert isinstance(client._server, seekdbclient.SeekdbServerClient)
243
+
244
+ # Test connection
245
+ try:
246
+ result = client._server.execute("SELECT 1 as test")
247
+ assert result is not None
248
+ except Exception as e:
249
+ pytest.skip(f"Server connection failed ({SERVER_HOST}:{SERVER_PORT}): {e}")
250
+
251
+ # Create test collection
252
+ collection_name = f"test_get_{int(time.time())}"
253
+ collection = client.create_collection(name=collection_name, dimension=3)
254
+
255
+ try:
256
+ # Insert test data and get IDs
257
+ inserted_ids = self._insert_test_data(client, collection_name)
258
+ assert len(inserted_ids) > 0, f"Failed to get inserted IDs. Expected at least 1, got {len(inserted_ids)}"
259
+ if len(inserted_ids) < 5:
260
+ print(f" Warning: Expected 5 inserted IDs, but got {len(inserted_ids)}")
261
+
262
+ # Test 1: Get by single ID
263
+ print(f"\n✅ Testing get by single ID for server client")
264
+ results = collection.get(ids=inserted_ids[0])
265
+ assert results is not None
266
+ assert len(results) == 1
267
+ print(f" Found {len(results)} result for ID={inserted_ids[0]}")
268
+
269
+ # Test 2: Get by multiple IDs
270
+ print(f"✅ Testing get by multiple IDs")
271
+ if len(inserted_ids) >= 3:
272
+ results = collection.get(ids=inserted_ids[:3])
273
+ assert results is not None
274
+ assert len(results) <= 3
275
+ print(f" Found {len(results)} results for IDs={inserted_ids[:3]}")
276
+
277
+ # Test 3: Get by metadata filter with comparison operator
278
+ print(f"✅ Testing get with metadata filter ($gte)")
279
+ results = collection.get(
280
+ where={"score": {"$gte": 90}},
281
+ limit=10
282
+ )
283
+ assert results is not None
284
+ assert len(results) > 0
285
+ print(f" Found {len(results)} results with score >= 90")
286
+
287
+ # Test 4: Get by combined metadata filters
288
+ print(f"✅ Testing get with combined metadata filters")
289
+ results = collection.get(
290
+ where={"category": {"$eq": "AI"}, "score": {"$gte": 90}},
291
+ limit=10
292
+ )
293
+ assert results is not None
294
+ print(f" Found {len(results)} results with category='AI' and score >= 90")
295
+
296
+ # Test 5: Get by document filter
297
+ print(f"✅ Testing get with document filter")
298
+ results = collection.get(
299
+ where_document={"$contains": "Python"},
300
+ limit=10
301
+ )
302
+ assert results is not None
303
+ print(f" Found {len(results)} results containing 'Python'")
304
+
305
+ # Test 6: Get with $in operator
306
+ print(f"✅ Testing get with $in operator")
307
+ results = collection.get(
308
+ where={"tag": {"$in": ["ml", "python"]}},
309
+ limit=10
310
+ )
311
+ assert results is not None
312
+ print(f" Found {len(results)} results with tag in ['ml', 'python']")
313
+
314
+ # Test 7: Get with limit and offset
315
+ print(f"✅ Testing get with limit and offset")
316
+ results = collection.get(limit=2, offset=1)
317
+ assert results is not None
318
+ assert len(results) <= 2
319
+ print(f" Found {len(results)} results (limit=2, offset=1)")
320
+
321
+ # Test 8: Get with include parameter
322
+ print(f"✅ Testing get with include parameter")
323
+ results = collection.get(
324
+ ids=inserted_ids[:2],
325
+ include=["documents", "metadatas", "embeddings"]
326
+ )
327
+ assert results is not None
328
+ assert isinstance(results, list), "Multiple IDs should return List[QueryResult]"
329
+ assert len(results) == 2, f"Expected 2 QueryResult objects, got {len(results)}"
330
+ for i, result in enumerate(results):
331
+ assert isinstance(result, seekdbclient.QueryResult), f"Result {i} should be QueryResult"
332
+ if len(result) > 0:
333
+ for item in result:
334
+ result_dict = item.to_dict() if hasattr(item, 'to_dict') else item
335
+ assert '_id' in result_dict
336
+ print(f" QueryResult {i} keys: {list(result[0].to_dict().keys()) if len(result) > 0 else 'empty'}")
337
+
338
+ # Test 9: Get by multiple IDs (should return List[QueryResult])
339
+ print(f"✅ Testing get by multiple IDs (returns List[QueryResult])")
340
+ if len(inserted_ids) >= 3:
341
+ results = collection.get(ids=inserted_ids[:3])
342
+ assert results is not None
343
+ assert isinstance(results, list), "Multiple IDs should return List[QueryResult]"
344
+ assert len(results) == 3, f"Expected 3 QueryResult objects, got {len(results)}"
345
+ for i, result in enumerate(results):
346
+ assert isinstance(result, seekdbclient.QueryResult), f"Result {i} should be QueryResult"
347
+ assert len(result) >= 0, f"QueryResult {i} should exist (may be empty if ID not found)"
348
+ print(f" QueryResult {i} for ID {inserted_ids[i]}: {len(result)} items")
349
+
350
+ # Test 10: Single ID still returns single QueryResult (backward compatibility)
351
+ print(f"✅ Testing single ID returns single QueryResult (backward compatibility)")
352
+ results = collection.get(ids=inserted_ids[0])
353
+ assert results is not None
354
+ assert isinstance(results, seekdbclient.QueryResult), "Single ID should return QueryResult, not list"
355
+ assert len(results) == 1
356
+ print(f" Single QueryResult with {len(results)} item")
357
+
358
+ # Test 11: Get with filters still returns single QueryResult (not multiple)
359
+ print(f"✅ Testing get with filters returns single QueryResult")
360
+ results = collection.get(
361
+ where={"category": {"$eq": "AI"}},
362
+ limit=10
363
+ )
364
+ assert results is not None
365
+ assert isinstance(results, seekdbclient.QueryResult), "Get with filters should return QueryResult, not list"
366
+ print(f" Single QueryResult with {len(results)} items matching filter")
367
+
368
+ finally:
369
+ # Cleanup
370
+ try:
371
+ client.delete_collection(name=collection_name)
372
+ print(f" Cleaned up collection: {collection_name}")
373
+ except Exception as cleanup_error:
374
+ print(f" Warning: Failed to cleanup collection: {cleanup_error}")
375
+
376
+ def test_oceanbase_collection_get(self):
377
+ """Test collection.get() with OceanBase client"""
378
+ # Create OceanBase client
379
+ client = seekdbclient.OBClient(
380
+ host=OB_HOST,
381
+ port=OB_PORT,
382
+ tenant=OB_TENANT,
383
+ database=OB_DATABASE,
384
+ user=OB_USER,
385
+ password=OB_PASSWORD
386
+ )
387
+
388
+ assert client is not None
389
+ assert hasattr(client, '_server')
390
+ assert isinstance(client._server, seekdbclient.OceanBaseServerClient)
391
+
392
+ # Test connection
393
+ try:
394
+ result = client._server.execute("SELECT 1 as test")
395
+ assert result is not None
396
+ except Exception as e:
397
+ pytest.skip(f"OceanBase connection failed ({OB_HOST}:{OB_PORT}): {e}")
398
+
399
+ # Create test collection
400
+ collection_name = f"test_get_{int(time.time())}"
401
+ collection = client.create_collection(name=collection_name, dimension=3)
402
+
403
+ try:
404
+ # Insert test data and get IDs
405
+ inserted_ids = self._insert_test_data(client, collection_name)
406
+ assert len(inserted_ids) > 0, f"Failed to get inserted IDs. Expected at least 1, got {len(inserted_ids)}"
407
+ if len(inserted_ids) < 5:
408
+ print(f" Warning: Expected 5 inserted IDs, but got {len(inserted_ids)}")
409
+
410
+ # Test 1: Get by single ID
411
+ print(f"\n✅ Testing get by single ID for OceanBase client")
412
+ results = collection.get(ids=inserted_ids[0])
413
+ assert results is not None
414
+ assert len(results) == 1
415
+ print(f" Found {len(results)} result for ID={inserted_ids[0]}")
416
+
417
+ # Test 2: Get by multiple IDs
418
+ print(f"✅ Testing get by multiple IDs")
419
+ if len(inserted_ids) >= 3:
420
+ results = collection.get(ids=inserted_ids[:3])
421
+ assert results is not None
422
+ assert len(results) <= 3
423
+ print(f" Found {len(results)} results for IDs={inserted_ids[:3]}")
424
+
425
+ # Test 3: Get by metadata filter
426
+ print(f"✅ Testing get with metadata filter")
427
+ results = collection.get(
428
+ where={"category": {"$eq": "AI"}},
429
+ limit=10
430
+ )
431
+ assert results is not None
432
+ assert len(results) > 0
433
+ print(f" Found {len(results)} results with category='AI'")
434
+
435
+ # Test 4: Get by logical operators ($or)
436
+ print(f"✅ Testing get with logical operators ($or)")
437
+ results = collection.get(
438
+ where={
439
+ "$or": [
440
+ {"category": {"$eq": "AI"}},
441
+ {"tag": {"$eq": "python"}}
442
+ ]
443
+ },
444
+ limit=10
445
+ )
446
+ assert results is not None
447
+ print(f" Found {len(results)} results with $or condition")
448
+
449
+ # Test 5: Get by document filter
450
+ print(f"✅ Testing get with document filter")
451
+ results = collection.get(
452
+ where_document={"$contains": "machine"},
453
+ limit=10
454
+ )
455
+ assert results is not None
456
+ print(f" Found {len(results)} results containing 'machine'")
457
+
458
+ # Test 6: Get with combined filters (where + where_document)
459
+ print(f"✅ Testing get with combined filters")
460
+ results = collection.get(
461
+ where={"category": {"$eq": "AI"}},
462
+ where_document={"$contains": "machine"},
463
+ limit=10
464
+ )
465
+ assert results is not None
466
+ print(f" Found {len(results)} results matching all filters")
467
+
468
+ # Test 7: Get with limit and offset
469
+ print(f"✅ Testing get with limit and offset")
470
+ results = collection.get(limit=2, offset=2)
471
+ assert results is not None
472
+ assert len(results) <= 2
473
+ print(f" Found {len(results)} results (limit=2, offset=2)")
474
+
475
+ # Test 8: Get all data without filters
476
+ print(f"✅ Testing get all data without filters")
477
+ results = collection.get(limit=100)
478
+ assert results is not None
479
+ assert len(results) > 0
480
+ print(f" Found {len(results)} total results")
481
+
482
+ # Test 9: Get with include parameter
483
+ print(f"✅ Testing get with include parameter")
484
+ results = collection.get(
485
+ ids=inserted_ids[:2],
486
+ include=["documents", "metadatas", "embeddings"]
487
+ )
488
+ assert results is not None
489
+ assert isinstance(results, list), "Multiple IDs should return List[QueryResult]"
490
+ assert len(results) == 2, f"Expected 2 QueryResult objects, got {len(results)}"
491
+ for i, result in enumerate(results):
492
+ assert isinstance(result, seekdbclient.QueryResult), f"Result {i} should be QueryResult"
493
+ if len(result) > 0:
494
+ for item in result:
495
+ result_dict = item.to_dict() if hasattr(item, 'to_dict') else item
496
+ assert '_id' in result_dict
497
+ print(f" QueryResult {i} keys: {list(result[0].to_dict().keys()) if len(result) > 0 else 'empty'}")
498
+
499
+ # Test 10: Get by multiple IDs (should return List[QueryResult])
500
+ print(f"✅ Testing get by multiple IDs (returns List[QueryResult])")
501
+ if len(inserted_ids) >= 3:
502
+ results = collection.get(ids=inserted_ids[:3])
503
+ assert results is not None
504
+ assert isinstance(results, list), "Multiple IDs should return List[QueryResult]"
505
+ assert len(results) == 3, f"Expected 3 QueryResult objects, got {len(results)}"
506
+ for i, result in enumerate(results):
507
+ assert isinstance(result, seekdbclient.QueryResult), f"Result {i} should be QueryResult"
508
+ assert len(result) >= 0, f"QueryResult {i} should exist (may be empty if ID not found)"
509
+ print(f" QueryResult {i} for ID {inserted_ids[i]}: {len(result)} items")
510
+
511
+ # Test 11: Single ID still returns single QueryResult (backward compatibility)
512
+ print(f"✅ Testing single ID returns single QueryResult (backward compatibility)")
513
+ results = collection.get(ids=inserted_ids[0])
514
+ assert results is not None
515
+ assert isinstance(results, seekdbclient.QueryResult), "Single ID should return QueryResult, not list"
516
+ assert len(results) == 1
517
+ print(f" Single QueryResult with {len(results)} item")
518
+
519
+ # Test 12: Get with filters still returns single QueryResult (not multiple)
520
+ print(f"✅ Testing get with filters returns single QueryResult")
521
+ results = collection.get(
522
+ where={"category": {"$eq": "AI"}},
523
+ limit=10
524
+ )
525
+ assert results is not None
526
+ assert isinstance(results, seekdbclient.QueryResult), "Get with filters should return QueryResult, not list"
527
+ print(f" Single QueryResult with {len(results)} items matching filter")
528
+
529
+ finally:
530
+ # Cleanup
531
+ try:
532
+ client.delete_collection(name=collection_name)
533
+ print(f" Cleaned up collection: {collection_name}")
534
+ except Exception as cleanup_error:
535
+ print(f" Warning: Failed to cleanup collection: {cleanup_error}")
536
+ pass
537
+
538
+
539
+ if __name__ == "__main__":
540
+ print("\n" + "="*60)
541
+ print("SeekDBClient - Collection Get Tests")
542
+ print("="*60)
543
+ print(f"\nEnvironment Variable Configuration:")
544
+ print(f" Embedded mode: path={SEEKDB_PATH}, database={SEEKDB_DATABASE}")
545
+ print(f" Server mode: {SERVER_USER}@{SERVER_HOST}:{SERVER_PORT}/{SERVER_DATABASE}")
546
+ print(f" OceanBase mode: {OB_USER}@{OB_TENANT} -> {OB_HOST}:{OB_PORT}/{OB_DATABASE}")
547
+ print("="*60 + "\n")
548
+
549
+ pytest.main([__file__, "-v", "-s"])
550
+