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,425 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Client creation and connection tests - testing connection and query execution 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
|
+
from pathlib import Path
|
|
10
|
+
|
|
11
|
+
# Add project path
|
|
12
|
+
project_root = Path(__file__).parent.parent.parent
|
|
13
|
+
sys.path.insert(0, str(project_root))
|
|
14
|
+
|
|
15
|
+
import seekdbclient
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
# ==================== Environment Variable Configuration ====================
|
|
19
|
+
# Embedded mode
|
|
20
|
+
SEEKDB_PATH = os.environ.get('SEEKDB_PATH', os.path.join(project_root, "seekdb_store"))
|
|
21
|
+
SEEKDB_DATABASE = os.environ.get('SEEKDB_DATABASE', 'test')
|
|
22
|
+
|
|
23
|
+
# Server mode
|
|
24
|
+
SERVER_HOST = os.environ.get('SERVER_HOST', 'localhost')
|
|
25
|
+
SERVER_PORT = int(os.environ.get('SERVER_PORT', '2881'))
|
|
26
|
+
SERVER_DATABASE = os.environ.get('SERVER_DATABASE', 'test')
|
|
27
|
+
SERVER_USER = os.environ.get('SERVER_USER', 'root')
|
|
28
|
+
SERVER_PASSWORD = os.environ.get('SERVER_PASSWORD', '')
|
|
29
|
+
|
|
30
|
+
# OceanBase mode
|
|
31
|
+
OB_HOST = os.environ.get('OB_HOST', 'localhost')
|
|
32
|
+
OB_PORT = int(os.environ.get('OB_PORT', '11202'))
|
|
33
|
+
OB_TENANT = os.environ.get('OB_TENANT', 'mysql')
|
|
34
|
+
OB_DATABASE = os.environ.get('OB_DATABASE', 'test')
|
|
35
|
+
OB_USER = os.environ.get('OB_USER', 'root')
|
|
36
|
+
OB_PASSWORD = os.environ.get('OB_PASSWORD', '')
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
class TestClientCreation:
|
|
40
|
+
"""Test client creation, connection, and query execution for all three modes"""
|
|
41
|
+
|
|
42
|
+
def _test_collection_management(self, client):
|
|
43
|
+
"""
|
|
44
|
+
Common test function for all collection management interfaces
|
|
45
|
+
|
|
46
|
+
Args:
|
|
47
|
+
client: Client proxy object with _server attribute
|
|
48
|
+
"""
|
|
49
|
+
# Test 1: create_collection - create a new collection
|
|
50
|
+
test_collection_name = "test_collection_" + str(int(time.time()))
|
|
51
|
+
test_dimension = 128
|
|
52
|
+
|
|
53
|
+
# Create collection
|
|
54
|
+
collection = client.create_collection(
|
|
55
|
+
name=test_collection_name,
|
|
56
|
+
dimension=test_dimension
|
|
57
|
+
)
|
|
58
|
+
|
|
59
|
+
# Verify collection object
|
|
60
|
+
assert collection is not None
|
|
61
|
+
assert collection.name == test_collection_name
|
|
62
|
+
assert collection.dimension == test_dimension
|
|
63
|
+
|
|
64
|
+
# Verify table was created by checking if it exists
|
|
65
|
+
table_name = f"c$v1${test_collection_name}"
|
|
66
|
+
try:
|
|
67
|
+
# Try to describe table structure to verify it exists
|
|
68
|
+
table_info = client._server.execute(f"DESCRIBE `{table_name}`")
|
|
69
|
+
assert table_info is not None
|
|
70
|
+
assert len(table_info) > 0
|
|
71
|
+
|
|
72
|
+
# Verify table has expected columns
|
|
73
|
+
# Handle both dict (server client) and tuple (embedded client) formats
|
|
74
|
+
column_names = []
|
|
75
|
+
column_types = {} # Store column types for verification
|
|
76
|
+
for row in table_info:
|
|
77
|
+
if isinstance(row, dict):
|
|
78
|
+
# Server client returns dict with 'Field' or 'field' key
|
|
79
|
+
field_name = row.get('Field', row.get('field', ''))
|
|
80
|
+
field_type = row.get('Type', row.get('type', ''))
|
|
81
|
+
column_names.append(field_name)
|
|
82
|
+
if field_name:
|
|
83
|
+
column_types[field_name] = str(field_type).lower()
|
|
84
|
+
elif isinstance(row, (tuple, list)):
|
|
85
|
+
# Embedded client returns tuple, first element is field name, second is type
|
|
86
|
+
field_name = row[0] if len(row) > 0 else ''
|
|
87
|
+
field_type = row[1] if len(row) > 1 else ''
|
|
88
|
+
column_names.append(field_name)
|
|
89
|
+
if field_name:
|
|
90
|
+
column_types[field_name] = str(field_type).lower()
|
|
91
|
+
else:
|
|
92
|
+
# Fallback: try to convert to string
|
|
93
|
+
column_names.append(str(row))
|
|
94
|
+
|
|
95
|
+
assert '_id' in column_names
|
|
96
|
+
assert 'document' in column_names
|
|
97
|
+
assert 'embedding' in column_names
|
|
98
|
+
assert 'metadata' in column_names
|
|
99
|
+
|
|
100
|
+
# Verify _id column type is varbinary (for varbinary(512) type)
|
|
101
|
+
if '_id' in column_types:
|
|
102
|
+
id_type = column_types['_id']
|
|
103
|
+
# Check if it's varbinary type (could be 'varbinary(512)' or similar)
|
|
104
|
+
assert 'varbinary' in id_type, f"Expected _id to be varbinary type, but got: {id_type}"
|
|
105
|
+
|
|
106
|
+
print(f"\n✅ Collection '{test_collection_name}' created successfully")
|
|
107
|
+
print(f" Table name: {table_name}")
|
|
108
|
+
print(f" Dimension: {test_dimension}")
|
|
109
|
+
print(f" Table columns: {', '.join(column_names)}")
|
|
110
|
+
|
|
111
|
+
except Exception as e:
|
|
112
|
+
# Clean up and fail
|
|
113
|
+
try:
|
|
114
|
+
client._server.execute(f"DROP TABLE IF EXISTS `{table_name}`")
|
|
115
|
+
except Exception:
|
|
116
|
+
pass
|
|
117
|
+
pytest.fail(f"Failed to verify collection table creation: {e}")
|
|
118
|
+
|
|
119
|
+
# Test 2: get_collection - get the collection we just created
|
|
120
|
+
retrieved_collection = client.get_collection(name=test_collection_name)
|
|
121
|
+
assert retrieved_collection is not None
|
|
122
|
+
assert retrieved_collection.name == test_collection_name
|
|
123
|
+
assert retrieved_collection.dimension == test_dimension
|
|
124
|
+
print(f"\n✅ Collection '{test_collection_name}' retrieved successfully")
|
|
125
|
+
print(f" Collection name: {retrieved_collection.name}")
|
|
126
|
+
print(f" Collection dimension: {retrieved_collection.dimension}")
|
|
127
|
+
|
|
128
|
+
# Test 3: has_collection - should return False for non-existent collection
|
|
129
|
+
non_existent_name = "test_collection_nonexistent_" + str(int(time.time()))
|
|
130
|
+
assert not client.has_collection(non_existent_name)
|
|
131
|
+
print(f"\n✅ has_collection correctly returns False for non-existent collection")
|
|
132
|
+
|
|
133
|
+
# Test 4: has_collection - should return True for existing collection
|
|
134
|
+
assert client.has_collection(test_collection_name)
|
|
135
|
+
print(f"\n✅ has_collection correctly returns True for existing collection")
|
|
136
|
+
|
|
137
|
+
# Test 5: get_or_create_collection - should get existing collection
|
|
138
|
+
existing_collection = client.get_or_create_collection(
|
|
139
|
+
name=test_collection_name,
|
|
140
|
+
dimension=test_dimension
|
|
141
|
+
)
|
|
142
|
+
assert existing_collection is not None
|
|
143
|
+
assert existing_collection.name == test_collection_name
|
|
144
|
+
assert existing_collection.dimension == test_dimension
|
|
145
|
+
print(f"\n✅ get_or_create_collection successfully retrieved existing collection")
|
|
146
|
+
|
|
147
|
+
# Test 6: get_or_create_collection - should create new collection
|
|
148
|
+
test_collection_name_mgmt = "test_collection_mgmt_" + str(int(time.time()))
|
|
149
|
+
new_collection = client.get_or_create_collection(
|
|
150
|
+
name=test_collection_name_mgmt,
|
|
151
|
+
dimension=test_dimension
|
|
152
|
+
)
|
|
153
|
+
assert new_collection is not None
|
|
154
|
+
assert new_collection.name == test_collection_name_mgmt
|
|
155
|
+
assert new_collection.dimension == test_dimension
|
|
156
|
+
print(f"\n✅ get_or_create_collection successfully created collection '{test_collection_name_mgmt}'")
|
|
157
|
+
|
|
158
|
+
# Test 7: list_collections - should include our collections
|
|
159
|
+
collections = client.list_collections()
|
|
160
|
+
assert isinstance(collections, list)
|
|
161
|
+
collection_names = [c.name for c in collections]
|
|
162
|
+
assert test_collection_name in collection_names
|
|
163
|
+
assert test_collection_name_mgmt in collection_names
|
|
164
|
+
print(f"\n✅ list_collections successfully listed collections: {len(collections)} found")
|
|
165
|
+
print(f" Collection names: {collection_names}")
|
|
166
|
+
|
|
167
|
+
# Test 8: delete_collection - should delete the collection
|
|
168
|
+
client.delete_collection(test_collection_name_mgmt)
|
|
169
|
+
assert not client.has_collection(test_collection_name_mgmt)
|
|
170
|
+
print(f"\n✅ delete_collection successfully deleted collection '{test_collection_name_mgmt}'")
|
|
171
|
+
|
|
172
|
+
# Test 9: delete_collection - should raise error for non-existent collection
|
|
173
|
+
try:
|
|
174
|
+
client.delete_collection(test_collection_name_mgmt)
|
|
175
|
+
pytest.fail("delete_collection should raise ValueError for non-existent collection")
|
|
176
|
+
except ValueError as e:
|
|
177
|
+
assert "does not exist" in str(e)
|
|
178
|
+
print(f"\n✅ delete_collection correctly raises ValueError for non-existent collection")
|
|
179
|
+
|
|
180
|
+
# Test 10: get_or_create_collection without dimension - should raise error for non-existent collection
|
|
181
|
+
try:
|
|
182
|
+
client.get_or_create_collection(name="non_existent_collection")
|
|
183
|
+
pytest.fail("get_or_create_collection should raise ValueError when collection doesn't exist and dimension is not provided")
|
|
184
|
+
except ValueError as e:
|
|
185
|
+
assert "dimension parameter is required" in str(e)
|
|
186
|
+
print(f"\n✅ get_or_create_collection correctly raises ValueError when dimension is missing")
|
|
187
|
+
|
|
188
|
+
# Test 11: count_collection - count the number of collections
|
|
189
|
+
collection_count = client.count_collection()
|
|
190
|
+
assert isinstance(collection_count, int)
|
|
191
|
+
assert collection_count >= 1 # At least the test collection we created
|
|
192
|
+
print(f"\n✅ count_collection successfully returned count: {collection_count}")
|
|
193
|
+
|
|
194
|
+
# Test 12: collection.count() - count items in collection (should be 0 for empty collection)
|
|
195
|
+
item_count = collection.count()
|
|
196
|
+
assert isinstance(item_count, int)
|
|
197
|
+
assert item_count == 0 # Collection is empty
|
|
198
|
+
print(f"\n✅ collection.count() successfully returned count: {item_count}")
|
|
199
|
+
|
|
200
|
+
# Test 13: collection.peek() - preview items in empty collection
|
|
201
|
+
preview = collection.peek(limit=5)
|
|
202
|
+
assert preview is not None
|
|
203
|
+
assert len(preview) == 0 # Empty collection
|
|
204
|
+
print(f"\n✅ collection.peek() successfully returned preview: {len(preview)} items")
|
|
205
|
+
|
|
206
|
+
# Add some test data to test count and peek with data
|
|
207
|
+
import uuid
|
|
208
|
+
test_ids = [str(uuid.uuid4()) for _ in range(3)]
|
|
209
|
+
# Use collection dimension for vectors
|
|
210
|
+
test_vectors = [[float(i + j) for j in range(test_dimension)] for i in range(3)]
|
|
211
|
+
collection.add(
|
|
212
|
+
ids=test_ids,
|
|
213
|
+
vectors=test_vectors,
|
|
214
|
+
documents=[f"Test document {i}" for i in range(3)],
|
|
215
|
+
metadatas=[{"index": i} for i in range(3)]
|
|
216
|
+
)
|
|
217
|
+
|
|
218
|
+
# Test 14: collection.count() - count items after adding data
|
|
219
|
+
item_count_after = collection.count()
|
|
220
|
+
assert item_count_after == 3
|
|
221
|
+
print(f"\n✅ collection.count() after adding data: {item_count_after} items")
|
|
222
|
+
|
|
223
|
+
# Test 15: collection.peek() - preview items with data
|
|
224
|
+
preview_with_data = collection.peek(limit=2)
|
|
225
|
+
assert preview_with_data is not None
|
|
226
|
+
assert len(preview_with_data) == 2 # Limited to 2 items
|
|
227
|
+
# Verify preview items have expected fields
|
|
228
|
+
for item in preview_with_data:
|
|
229
|
+
assert hasattr(item, '_id')
|
|
230
|
+
assert hasattr(item, 'document')
|
|
231
|
+
assert hasattr(item, 'metadata')
|
|
232
|
+
print(f"\n✅ collection.peek() with data returned {len(preview_with_data)} items")
|
|
233
|
+
|
|
234
|
+
# Test 16: collection.peek() with different limit
|
|
235
|
+
preview_all = collection.peek(limit=10)
|
|
236
|
+
assert len(preview_all) == 3 # All 3 items
|
|
237
|
+
print(f"\n✅ collection.peek(limit=10) returned {len(preview_all)} items")
|
|
238
|
+
|
|
239
|
+
# Clean up: delete the test collection table
|
|
240
|
+
try:
|
|
241
|
+
client._server.execute(f"DROP TABLE IF EXISTS `{table_name}`")
|
|
242
|
+
print(f" Cleaned up test table: {table_name}")
|
|
243
|
+
except Exception as cleanup_error:
|
|
244
|
+
print(f" Warning: Failed to cleanup test table: {cleanup_error}")
|
|
245
|
+
|
|
246
|
+
def test_create_embedded_client(self):
|
|
247
|
+
"""Test creating embedded client (lazy loading) and executing queries"""
|
|
248
|
+
if not os.path.exists(SEEKDB_PATH):
|
|
249
|
+
pytest.fail(
|
|
250
|
+
f"❌ SeekDB data directory does not exist: {SEEKDB_PATH}\n\n"
|
|
251
|
+
f"Solution:\n"
|
|
252
|
+
f" 1. Create the directory: mkdir -p {SEEKDB_PATH}\n"
|
|
253
|
+
f" 2. Or set SEEKDB_PATH environment variable to an existing directory:\n"
|
|
254
|
+
f" export SEEKDB_PATH=/path/to/your/seekdb/data\n"
|
|
255
|
+
f" python3 -m pytest seekdbclient/tests/test_client_creation.py -v -s"
|
|
256
|
+
)
|
|
257
|
+
|
|
258
|
+
# Check if seekdb package is available and properly configured
|
|
259
|
+
try:
|
|
260
|
+
import sys
|
|
261
|
+
project_root_str = str(project_root)
|
|
262
|
+
if project_root_str in sys.path:
|
|
263
|
+
sys.path.remove(project_root_str)
|
|
264
|
+
import seekdb
|
|
265
|
+
if not hasattr(seekdb, 'open') and not hasattr(seekdb, '_initialize_module'):
|
|
266
|
+
pytest.fail(
|
|
267
|
+
"❌ SeekDB embedded package is not properly installed!\n"
|
|
268
|
+
"The 'seekdb' module exists but lacks required methods.\n"
|
|
269
|
+
"Required: 'open' method or '_initialize_module' method\n\n"
|
|
270
|
+
"Solution: Please install the seekdb embedded package from correct source:\n"
|
|
271
|
+
" pip install seekdb\n"
|
|
272
|
+
"Or contact the seekdb package maintainer for installation guide."
|
|
273
|
+
)
|
|
274
|
+
except ImportError:
|
|
275
|
+
pytest.fail(
|
|
276
|
+
"❌ SeekDB embedded package is not installed!\n"
|
|
277
|
+
"The 'seekdb' module cannot be imported.\n\n"
|
|
278
|
+
"Solution: Please install the seekdb embedded package:\n"
|
|
279
|
+
" pip install seekdb\n"
|
|
280
|
+
"Or contact the seekdb package maintainer for installation guide."
|
|
281
|
+
)
|
|
282
|
+
finally:
|
|
283
|
+
if project_root_str not in sys.path:
|
|
284
|
+
sys.path.insert(0, project_root_str)
|
|
285
|
+
|
|
286
|
+
# Create client (returns _ClientProxy)
|
|
287
|
+
client = seekdbclient.Client(
|
|
288
|
+
path=SEEKDB_PATH,
|
|
289
|
+
database=SEEKDB_DATABASE
|
|
290
|
+
)
|
|
291
|
+
|
|
292
|
+
# Verify client type and properties
|
|
293
|
+
assert client is not None
|
|
294
|
+
# Client now returns a proxy
|
|
295
|
+
assert hasattr(client, '_server')
|
|
296
|
+
assert isinstance(client._server, seekdbclient.SeekdbEmbeddedClient)
|
|
297
|
+
assert client._server.mode == "SeekdbEmbeddedClient"
|
|
298
|
+
assert client._server.database == SEEKDB_DATABASE
|
|
299
|
+
|
|
300
|
+
# Should not be connected at this point (lazy loading)
|
|
301
|
+
assert not client._server.is_connected()
|
|
302
|
+
|
|
303
|
+
# Execute query through proxy (first use, triggers connection)
|
|
304
|
+
result = client._server.execute("SELECT 1")
|
|
305
|
+
assert result is not None
|
|
306
|
+
assert len(result) > 0
|
|
307
|
+
|
|
308
|
+
# Should be connected now
|
|
309
|
+
assert client._server.is_connected()
|
|
310
|
+
|
|
311
|
+
print(f"\n✅ Embedded client created and connected successfully: path={SEEKDB_PATH}, database={SEEKDB_DATABASE}")
|
|
312
|
+
print(f" Query result: {result[0]}")
|
|
313
|
+
|
|
314
|
+
# Test all collection management interfaces
|
|
315
|
+
self._test_collection_management(client)
|
|
316
|
+
|
|
317
|
+
# Automatic cleanup (via __del__)
|
|
318
|
+
|
|
319
|
+
def test_create_server_client(self):
|
|
320
|
+
"""Test creating server client (lazy loading) and executing queries"""
|
|
321
|
+
# Create client (returns _ClientProxy)
|
|
322
|
+
client = seekdbclient.Client(
|
|
323
|
+
host=SERVER_HOST,
|
|
324
|
+
port=SERVER_PORT,
|
|
325
|
+
database=SERVER_DATABASE,
|
|
326
|
+
user=SERVER_USER,
|
|
327
|
+
password=SERVER_PASSWORD
|
|
328
|
+
)
|
|
329
|
+
|
|
330
|
+
# Verify client type and properties
|
|
331
|
+
assert client is not None
|
|
332
|
+
assert hasattr(client, '_server')
|
|
333
|
+
assert isinstance(client._server, seekdbclient.SeekdbServerClient)
|
|
334
|
+
assert client._server.mode == "SeekdbServerClient"
|
|
335
|
+
assert client._server.host == SERVER_HOST
|
|
336
|
+
assert client._server.port == SERVER_PORT
|
|
337
|
+
assert client._server.database == SERVER_DATABASE
|
|
338
|
+
assert client._server.user == SERVER_USER
|
|
339
|
+
|
|
340
|
+
# Should not be connected at this point (lazy loading)
|
|
341
|
+
assert not client._server.is_connected()
|
|
342
|
+
|
|
343
|
+
# Execute query through proxy (first use, triggers connection)
|
|
344
|
+
try:
|
|
345
|
+
result = client._server.execute("SELECT 1 as test")
|
|
346
|
+
assert result is not None
|
|
347
|
+
assert len(result) > 0
|
|
348
|
+
assert result[0]['test'] == 1
|
|
349
|
+
|
|
350
|
+
# Should be connected now
|
|
351
|
+
assert client._server.is_connected()
|
|
352
|
+
|
|
353
|
+
print(f"\n✅ Server client created and connected successfully: {SERVER_USER}@{SERVER_HOST}:{SERVER_PORT}/{SERVER_DATABASE}")
|
|
354
|
+
print(f" Query result: {result[0]}")
|
|
355
|
+
|
|
356
|
+
# Test all collection management interfaces
|
|
357
|
+
self._test_collection_management(client)
|
|
358
|
+
|
|
359
|
+
except Exception as e:
|
|
360
|
+
pytest.fail(f"Server connection failed ({SERVER_HOST}:{SERVER_PORT}): {e}\n"
|
|
361
|
+
f"Hint: Please ensure SeekDB Server is running on port {SERVER_PORT}")
|
|
362
|
+
|
|
363
|
+
# Automatic cleanup (via __del__)
|
|
364
|
+
|
|
365
|
+
def test_create_oceanbase_client(self):
|
|
366
|
+
"""Test creating OceanBase client (lazy loading) and executing queries"""
|
|
367
|
+
# Create client (returns _ClientProxy)
|
|
368
|
+
client = seekdbclient.OBClient(
|
|
369
|
+
host=OB_HOST,
|
|
370
|
+
port=OB_PORT,
|
|
371
|
+
tenant=OB_TENANT,
|
|
372
|
+
database=OB_DATABASE,
|
|
373
|
+
user=OB_USER,
|
|
374
|
+
password=OB_PASSWORD
|
|
375
|
+
)
|
|
376
|
+
|
|
377
|
+
# Verify client type and properties
|
|
378
|
+
assert client is not None
|
|
379
|
+
assert hasattr(client, '_server')
|
|
380
|
+
assert isinstance(client._server, seekdbclient.OceanBaseServerClient)
|
|
381
|
+
assert client._server.mode == "OceanBaseServerClient"
|
|
382
|
+
assert client._server.host == OB_HOST
|
|
383
|
+
assert client._server.port == OB_PORT
|
|
384
|
+
assert client._server.tenant == OB_TENANT
|
|
385
|
+
assert client._server.database == OB_DATABASE
|
|
386
|
+
assert client._server.user == OB_USER
|
|
387
|
+
assert client._server.full_user == f"{OB_USER}@{OB_TENANT}"
|
|
388
|
+
|
|
389
|
+
# Should not be connected at this point (lazy loading)
|
|
390
|
+
assert not client._server.is_connected()
|
|
391
|
+
|
|
392
|
+
# Execute query through proxy (first use, triggers connection)
|
|
393
|
+
try:
|
|
394
|
+
result = client._server.execute("SELECT 1 as test")
|
|
395
|
+
assert result is not None
|
|
396
|
+
assert len(result) > 0
|
|
397
|
+
assert result[0]['test'] == 1
|
|
398
|
+
|
|
399
|
+
# Should be connected now
|
|
400
|
+
assert client._server.is_connected()
|
|
401
|
+
|
|
402
|
+
print(f"\n✅ OceanBase client created and connected successfully: {client._server.full_user}@{OB_HOST}:{OB_PORT}/{OB_DATABASE}")
|
|
403
|
+
print(f" Query result: {result[0]}")
|
|
404
|
+
|
|
405
|
+
# Test all collection management interfaces
|
|
406
|
+
self._test_collection_management(client)
|
|
407
|
+
|
|
408
|
+
except Exception as e:
|
|
409
|
+
pytest.fail(f"OceanBase connection failed ({OB_HOST}:{OB_PORT}): {e}\n"
|
|
410
|
+
f"Hint: Please ensure OceanBase is running and tenant '{OB_TENANT}' is created")
|
|
411
|
+
|
|
412
|
+
# Automatic cleanup (via __del__)
|
|
413
|
+
|
|
414
|
+
|
|
415
|
+
if __name__ == "__main__":
|
|
416
|
+
print("\n" + "="*60)
|
|
417
|
+
print("SeekDBClient - Client Creation and Connection Tests")
|
|
418
|
+
print("="*60)
|
|
419
|
+
print(f"\nEnvironment Variable Configuration:")
|
|
420
|
+
print(f" Embedded mode: path={SEEKDB_PATH}, database={SEEKDB_DATABASE}")
|
|
421
|
+
print(f" Server mode: {SERVER_USER}@{SERVER_HOST}:{SERVER_PORT}/{SERVER_DATABASE}")
|
|
422
|
+
print(f" OceanBase mode: {OB_USER}@{OB_TENANT} -> {OB_HOST}:{OB_PORT}/{OB_DATABASE}")
|
|
423
|
+
print("="*60 + "\n")
|
|
424
|
+
|
|
425
|
+
pytest.main([__file__, "-v", "-s"])
|