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
pyseekdb/__init__.py
ADDED
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
"""
|
|
2
|
+
SeekDBClient - Unified vector database client wrapper
|
|
3
|
+
|
|
4
|
+
Based on seekdb and pymysql, providing a simple and unified API.
|
|
5
|
+
|
|
6
|
+
Supports three modes:
|
|
7
|
+
1. Embedded mode - using local seekdb
|
|
8
|
+
2. Server mode - connecting to remote seekdb via pymysql
|
|
9
|
+
3. OceanBase mode - connecting to OceanBase via pymysql
|
|
10
|
+
|
|
11
|
+
Examples:
|
|
12
|
+
>>> import seekdbclient
|
|
13
|
+
|
|
14
|
+
>>> # Embedded mode - Collection management
|
|
15
|
+
>>> client = seekdbclient.Client(path="./seekdb", database="test")
|
|
16
|
+
|
|
17
|
+
>>> # Server mode - Collection management
|
|
18
|
+
>>> client = seekdbclient.Client(
|
|
19
|
+
... host='localhost',
|
|
20
|
+
... port=2881,
|
|
21
|
+
... database="test",
|
|
22
|
+
... user="root",
|
|
23
|
+
... password="pass"
|
|
24
|
+
... )
|
|
25
|
+
|
|
26
|
+
>>> # OceanBase mode - Collection management
|
|
27
|
+
>>> ob_client = seekdbclient.OBClient(
|
|
28
|
+
... host='localhost',
|
|
29
|
+
... port=2881,
|
|
30
|
+
... tenant="test",
|
|
31
|
+
... database="test",
|
|
32
|
+
... user="root",
|
|
33
|
+
... password=""
|
|
34
|
+
... )
|
|
35
|
+
|
|
36
|
+
>>> # Admin client - Database management
|
|
37
|
+
>>> admin = seekdbclient.AdminClient(path="./seekdb")
|
|
38
|
+
>>> admin.create_database("new_db")
|
|
39
|
+
>>> databases = admin.list_databases()
|
|
40
|
+
"""
|
|
41
|
+
import logging
|
|
42
|
+
import importlib.metadata
|
|
43
|
+
|
|
44
|
+
from .client import (
|
|
45
|
+
BaseConnection,
|
|
46
|
+
BaseClient,
|
|
47
|
+
ClientAPI,
|
|
48
|
+
SeekdbEmbeddedClient,
|
|
49
|
+
SeekdbServerClient,
|
|
50
|
+
OceanBaseServerClient,
|
|
51
|
+
Client,
|
|
52
|
+
OBClient,
|
|
53
|
+
AdminAPI,
|
|
54
|
+
AdminClient,
|
|
55
|
+
OBAdminClient,
|
|
56
|
+
Database,
|
|
57
|
+
)
|
|
58
|
+
from .client.collection import Collection
|
|
59
|
+
from .client.query_result import QueryResult
|
|
60
|
+
|
|
61
|
+
try:
|
|
62
|
+
__version__ = importlib.metadata.version("seekdbclient")
|
|
63
|
+
except importlib.metadata.PackageNotFoundError:
|
|
64
|
+
__version__ = "0.0.1.dev1"
|
|
65
|
+
|
|
66
|
+
__author__ = "SeekDBClient Team"
|
|
67
|
+
|
|
68
|
+
__all__ = [
|
|
69
|
+
'BaseConnection',
|
|
70
|
+
'BaseClient',
|
|
71
|
+
'ClientAPI',
|
|
72
|
+
'SeekdbEmbeddedClient',
|
|
73
|
+
'SeekdbServerClient',
|
|
74
|
+
'OceanBaseServerClient',
|
|
75
|
+
'Client',
|
|
76
|
+
'OBClient',
|
|
77
|
+
'Collection',
|
|
78
|
+
'QueryResult',
|
|
79
|
+
'AdminAPI',
|
|
80
|
+
'AdminClient',
|
|
81
|
+
'OBAdminClient',
|
|
82
|
+
'Database',
|
|
83
|
+
]
|
|
84
|
+
|
|
85
|
+
# Configure logging
|
|
86
|
+
logging.basicConfig(
|
|
87
|
+
level=logging.INFO,
|
|
88
|
+
format='%(asctime)s - %(name)s - %(levelname)s - %(message)s'
|
|
89
|
+
)
|
|
90
|
+
|
|
@@ -0,0 +1,324 @@
|
|
|
1
|
+
"""
|
|
2
|
+
SeekDBClient client module
|
|
3
|
+
|
|
4
|
+
Provides client and admin factory functions with strict separation:
|
|
5
|
+
|
|
6
|
+
Collection Management (ClientAPI):
|
|
7
|
+
- Client() - Smart factory for Embedded/Server mode
|
|
8
|
+
- OBClient() - OceanBase client factory
|
|
9
|
+
- Returns: _ClientProxy (collection operations only)
|
|
10
|
+
|
|
11
|
+
Database Management (AdminAPI):
|
|
12
|
+
- AdminClient() - Smart factory for Embedded/Server mode
|
|
13
|
+
- OBAdminClient() - OceanBase admin factory
|
|
14
|
+
- Returns: _AdminClientProxy (database operations only)
|
|
15
|
+
|
|
16
|
+
All factories use the underlying ServerAPI implementations:
|
|
17
|
+
- SeekdbEmbeddedClient - Local seekdb
|
|
18
|
+
- SeekdbServerClient - Remote seekdb via pymysql
|
|
19
|
+
- OceanBaseServerClient - OceanBase via pymysql
|
|
20
|
+
"""
|
|
21
|
+
import logging
|
|
22
|
+
from typing import Optional, Union
|
|
23
|
+
|
|
24
|
+
from .base_connection import BaseConnection
|
|
25
|
+
from .client_base import BaseClient, ClientAPI
|
|
26
|
+
from .client_seekdb_embedded import SeekdbEmbeddedClient
|
|
27
|
+
from .client_seekdb_server import SeekdbServerClient
|
|
28
|
+
from .client_oceanbase_server import OceanBaseServerClient
|
|
29
|
+
from .admin_client import AdminAPI, _AdminClientProxy, _ClientProxy
|
|
30
|
+
from .database import Database
|
|
31
|
+
|
|
32
|
+
logger = logging.getLogger(__name__)
|
|
33
|
+
|
|
34
|
+
__all__ = [
|
|
35
|
+
'BaseConnection',
|
|
36
|
+
'BaseClient',
|
|
37
|
+
'ClientAPI',
|
|
38
|
+
'SeekdbEmbeddedClient',
|
|
39
|
+
'SeekdbServerClient',
|
|
40
|
+
'OceanBaseServerClient',
|
|
41
|
+
'Client',
|
|
42
|
+
'OBClient',
|
|
43
|
+
'AdminAPI',
|
|
44
|
+
'AdminClient',
|
|
45
|
+
'OBAdminClient',
|
|
46
|
+
'Database',
|
|
47
|
+
]
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
def Client(
|
|
51
|
+
path: Optional[str] = None,
|
|
52
|
+
host: Optional[str] = None,
|
|
53
|
+
port: Optional[int] = None,
|
|
54
|
+
database: str = "test",
|
|
55
|
+
user: Optional[str] = None,
|
|
56
|
+
password: str = "",
|
|
57
|
+
**kwargs
|
|
58
|
+
) -> _ClientProxy:
|
|
59
|
+
"""
|
|
60
|
+
Smart client factory function (returns ClientProxy for collection operations only)
|
|
61
|
+
|
|
62
|
+
Automatically selects embedded or server mode based on parameters:
|
|
63
|
+
- If path is provided, uses embedded mode
|
|
64
|
+
- If host/port is provided, uses server mode
|
|
65
|
+
|
|
66
|
+
Returns a ClientProxy that only exposes collection operations.
|
|
67
|
+
For database management, use AdminClient().
|
|
68
|
+
|
|
69
|
+
Args:
|
|
70
|
+
path: seekdb data directory path (embedded mode)
|
|
71
|
+
host: server address (server mode)
|
|
72
|
+
port: server port (server mode)
|
|
73
|
+
database: database name
|
|
74
|
+
user: username (server mode)
|
|
75
|
+
password: password (server mode)
|
|
76
|
+
**kwargs: other parameters
|
|
77
|
+
|
|
78
|
+
Returns:
|
|
79
|
+
_ClientProxy: A proxy that only exposes collection operations
|
|
80
|
+
|
|
81
|
+
Examples:
|
|
82
|
+
>>> # Embedded mode
|
|
83
|
+
>>> client = Client(path="/path/to/seekdb", database="db1")
|
|
84
|
+
>>> client.create_collection("my_collection") # ✅ Available
|
|
85
|
+
>>> # client.create_database("new_db") # ❌ Not available
|
|
86
|
+
|
|
87
|
+
>>> # Server mode
|
|
88
|
+
>>> client = Client(
|
|
89
|
+
... host='localhost',
|
|
90
|
+
... port=2882,
|
|
91
|
+
... database="db1",
|
|
92
|
+
... user="u01",
|
|
93
|
+
... password="pass"
|
|
94
|
+
... )
|
|
95
|
+
"""
|
|
96
|
+
# Determine mode and create appropriate server
|
|
97
|
+
if path is not None:
|
|
98
|
+
# Embedded mode
|
|
99
|
+
logger.info(f"Creating embedded client: path={path}, database={database}")
|
|
100
|
+
server = SeekdbEmbeddedClient(
|
|
101
|
+
path=path,
|
|
102
|
+
database=database,
|
|
103
|
+
**kwargs
|
|
104
|
+
)
|
|
105
|
+
|
|
106
|
+
elif host is not None:
|
|
107
|
+
# Server mode
|
|
108
|
+
if port is None:
|
|
109
|
+
port = 2882 # Default port
|
|
110
|
+
if user is None:
|
|
111
|
+
user = "root"
|
|
112
|
+
|
|
113
|
+
logger.info(
|
|
114
|
+
f"Creating server mode client: {user}@{host}:{port}/{database}"
|
|
115
|
+
)
|
|
116
|
+
server = SeekdbServerClient(
|
|
117
|
+
host=host,
|
|
118
|
+
port=port,
|
|
119
|
+
database=database,
|
|
120
|
+
user=user,
|
|
121
|
+
password=password,
|
|
122
|
+
**kwargs
|
|
123
|
+
)
|
|
124
|
+
|
|
125
|
+
else:
|
|
126
|
+
raise ValueError(
|
|
127
|
+
"Must provide either path (embedded mode) or host (server mode) parameter"
|
|
128
|
+
)
|
|
129
|
+
|
|
130
|
+
# Return ClientProxy (only exposes collection operations)
|
|
131
|
+
return _ClientProxy(server=server)
|
|
132
|
+
|
|
133
|
+
|
|
134
|
+
def OBClient(
|
|
135
|
+
host: str = "localhost",
|
|
136
|
+
port: int = 2881,
|
|
137
|
+
tenant: str = "test",
|
|
138
|
+
database: str = "test",
|
|
139
|
+
user: str = "root",
|
|
140
|
+
password: str = "",
|
|
141
|
+
**kwargs
|
|
142
|
+
) -> _ClientProxy:
|
|
143
|
+
"""
|
|
144
|
+
OceanBase client factory function (returns ClientProxy for collection operations only)
|
|
145
|
+
|
|
146
|
+
Returns a ClientProxy that only exposes collection operations.
|
|
147
|
+
For database management, use OBAdminClient().
|
|
148
|
+
|
|
149
|
+
Args:
|
|
150
|
+
host: server address
|
|
151
|
+
port: server port (default 2881)
|
|
152
|
+
tenant: tenant name
|
|
153
|
+
database: database name
|
|
154
|
+
user: username (without tenant suffix)
|
|
155
|
+
password: password
|
|
156
|
+
**kwargs: other parameters
|
|
157
|
+
|
|
158
|
+
Returns:
|
|
159
|
+
_ClientProxy: A proxy that only exposes collection operations
|
|
160
|
+
|
|
161
|
+
Examples:
|
|
162
|
+
>>> client = OBClient(
|
|
163
|
+
... host='localhost',
|
|
164
|
+
... port=2881,
|
|
165
|
+
... tenant="tenant1",
|
|
166
|
+
... database="db1",
|
|
167
|
+
... user="u01",
|
|
168
|
+
... password="pass"
|
|
169
|
+
... )
|
|
170
|
+
>>> client.create_collection("my_collection") # ✅ Available
|
|
171
|
+
>>> # client.create_database("new_db") # ❌ Not available
|
|
172
|
+
"""
|
|
173
|
+
logger.info(
|
|
174
|
+
f"Creating OceanBase client: {user}@{tenant}@{host}:{port}/{database}"
|
|
175
|
+
)
|
|
176
|
+
|
|
177
|
+
server = OceanBaseServerClient(
|
|
178
|
+
host=host,
|
|
179
|
+
port=port,
|
|
180
|
+
tenant=tenant,
|
|
181
|
+
database=database,
|
|
182
|
+
user=user,
|
|
183
|
+
password=password,
|
|
184
|
+
**kwargs
|
|
185
|
+
)
|
|
186
|
+
|
|
187
|
+
# Return ClientProxy (only exposes collection operations)
|
|
188
|
+
return _ClientProxy(server=server)
|
|
189
|
+
|
|
190
|
+
|
|
191
|
+
def AdminClient(
|
|
192
|
+
path: Optional[str] = None,
|
|
193
|
+
host: Optional[str] = None,
|
|
194
|
+
port: Optional[int] = None,
|
|
195
|
+
user: Optional[str] = None,
|
|
196
|
+
password: str = "",
|
|
197
|
+
**kwargs
|
|
198
|
+
) -> _AdminClientProxy:
|
|
199
|
+
"""
|
|
200
|
+
Smart admin client factory function (proxy pattern)
|
|
201
|
+
|
|
202
|
+
Automatically selects embedded or server mode based on parameters:
|
|
203
|
+
- If path is provided, uses embedded mode
|
|
204
|
+
- If host/port is provided, uses server mode
|
|
205
|
+
|
|
206
|
+
Returns a lightweight AdminClient proxy that only exposes database operations.
|
|
207
|
+
For collection management, use Client().
|
|
208
|
+
|
|
209
|
+
Args:
|
|
210
|
+
path: seekdb data directory path (embedded mode)
|
|
211
|
+
host: server address (server mode)
|
|
212
|
+
port: server port (server mode)
|
|
213
|
+
user: username (server mode)
|
|
214
|
+
password: password (server mode)
|
|
215
|
+
**kwargs: other parameters
|
|
216
|
+
|
|
217
|
+
Returns:
|
|
218
|
+
_AdminClientProxy: A proxy that only exposes database operations
|
|
219
|
+
|
|
220
|
+
Examples:
|
|
221
|
+
>>> # Embedded mode
|
|
222
|
+
>>> admin = AdminClient(path="/path/to/seekdb")
|
|
223
|
+
>>> admin.create_database("new_db") # ✅ Available
|
|
224
|
+
>>> # admin.create_collection("coll") # ❌ Not available
|
|
225
|
+
|
|
226
|
+
>>> # Server mode
|
|
227
|
+
>>> admin = AdminClient(
|
|
228
|
+
... host='localhost',
|
|
229
|
+
... port=2882,
|
|
230
|
+
... user="root",
|
|
231
|
+
... password="pass"
|
|
232
|
+
... )
|
|
233
|
+
"""
|
|
234
|
+
# Determine mode and create appropriate server
|
|
235
|
+
if path is not None:
|
|
236
|
+
# Embedded mode
|
|
237
|
+
logger.info(f"Creating embedded admin client: path={path}")
|
|
238
|
+
server = SeekdbEmbeddedClient(
|
|
239
|
+
path=path,
|
|
240
|
+
database="information_schema", # Use system database for admin operations
|
|
241
|
+
**kwargs
|
|
242
|
+
)
|
|
243
|
+
|
|
244
|
+
elif host is not None:
|
|
245
|
+
# Server mode
|
|
246
|
+
if port is None:
|
|
247
|
+
port = 2882 # Default port
|
|
248
|
+
if user is None:
|
|
249
|
+
user = "root"
|
|
250
|
+
|
|
251
|
+
logger.info(
|
|
252
|
+
f"Creating server mode admin client: {user}@{host}:{port}"
|
|
253
|
+
)
|
|
254
|
+
server = SeekdbServerClient(
|
|
255
|
+
host=host,
|
|
256
|
+
port=port,
|
|
257
|
+
database="information_schema", # Use system database
|
|
258
|
+
user=user,
|
|
259
|
+
password=password,
|
|
260
|
+
**kwargs
|
|
261
|
+
)
|
|
262
|
+
|
|
263
|
+
else:
|
|
264
|
+
raise ValueError(
|
|
265
|
+
"Must provide either path (embedded mode) or host (server mode) parameter"
|
|
266
|
+
)
|
|
267
|
+
|
|
268
|
+
# Return AdminClient proxy (only exposes database operations)
|
|
269
|
+
return _AdminClientProxy(server=server)
|
|
270
|
+
|
|
271
|
+
|
|
272
|
+
def OBAdminClient(
|
|
273
|
+
host: str = "localhost",
|
|
274
|
+
port: int = 2881,
|
|
275
|
+
tenant: str = "test",
|
|
276
|
+
user: str = "root",
|
|
277
|
+
password: str = "",
|
|
278
|
+
**kwargs
|
|
279
|
+
) -> _AdminClientProxy:
|
|
280
|
+
"""
|
|
281
|
+
OceanBase admin client factory function (proxy pattern)
|
|
282
|
+
|
|
283
|
+
Returns a lightweight AdminClient proxy that only exposes database operations.
|
|
284
|
+
For collection management, use OBClient().
|
|
285
|
+
|
|
286
|
+
Args:
|
|
287
|
+
host: server address
|
|
288
|
+
port: server port (default 2881)
|
|
289
|
+
tenant: tenant name
|
|
290
|
+
user: username (without tenant suffix)
|
|
291
|
+
password: password
|
|
292
|
+
**kwargs: other parameters
|
|
293
|
+
|
|
294
|
+
Returns:
|
|
295
|
+
_AdminClientProxy: A proxy that only exposes database operations
|
|
296
|
+
|
|
297
|
+
Examples:
|
|
298
|
+
>>> admin = OBAdminClient(
|
|
299
|
+
... host='localhost',
|
|
300
|
+
... port=2881,
|
|
301
|
+
... tenant="tenant1",
|
|
302
|
+
... user="root",
|
|
303
|
+
... password="pass"
|
|
304
|
+
... )
|
|
305
|
+
>>> admin.create_database("new_db") # ✅ Available
|
|
306
|
+
>>> # admin.create_collection("coll") # ❌ Not available
|
|
307
|
+
"""
|
|
308
|
+
logger.info(
|
|
309
|
+
f"Creating OceanBase admin client: {user}@{tenant}@{host}:{port}"
|
|
310
|
+
)
|
|
311
|
+
|
|
312
|
+
server = OceanBaseServerClient(
|
|
313
|
+
host=host,
|
|
314
|
+
port=port,
|
|
315
|
+
tenant=tenant,
|
|
316
|
+
database="information_schema", # Use system database
|
|
317
|
+
user=user,
|
|
318
|
+
password=password,
|
|
319
|
+
**kwargs
|
|
320
|
+
)
|
|
321
|
+
|
|
322
|
+
# Return AdminClient proxy (only exposes database operations)
|
|
323
|
+
return _AdminClientProxy(server=server)
|
|
324
|
+
|
|
@@ -0,0 +1,202 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Admin client interface and implementation for database management
|
|
3
|
+
|
|
4
|
+
Also includes ClientProxy for strict separation of Collection vs Database operations
|
|
5
|
+
"""
|
|
6
|
+
from abc import ABC, abstractmethod
|
|
7
|
+
from typing import List, Optional, Sequence, TYPE_CHECKING
|
|
8
|
+
|
|
9
|
+
from .database import Database
|
|
10
|
+
|
|
11
|
+
if TYPE_CHECKING:
|
|
12
|
+
from .client_base import BaseClient, ClientAPI
|
|
13
|
+
from .collection import Collection
|
|
14
|
+
|
|
15
|
+
DEFAULT_TENANT = "test"
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
class AdminAPI(ABC):
|
|
19
|
+
"""
|
|
20
|
+
Abstract admin API interface for database management.
|
|
21
|
+
Defines the contract for database operations.
|
|
22
|
+
"""
|
|
23
|
+
|
|
24
|
+
@abstractmethod
|
|
25
|
+
def create_database(self, name: str, tenant: str = DEFAULT_TENANT) -> None:
|
|
26
|
+
"""
|
|
27
|
+
Create database
|
|
28
|
+
|
|
29
|
+
Args:
|
|
30
|
+
name: database name
|
|
31
|
+
tenant: tenant name (for OceanBase)
|
|
32
|
+
"""
|
|
33
|
+
pass
|
|
34
|
+
|
|
35
|
+
@abstractmethod
|
|
36
|
+
def get_database(self, name: str, tenant: str = DEFAULT_TENANT) -> Database:
|
|
37
|
+
"""
|
|
38
|
+
Get database object
|
|
39
|
+
|
|
40
|
+
Args:
|
|
41
|
+
name: database name
|
|
42
|
+
tenant: tenant name (for OceanBase)
|
|
43
|
+
|
|
44
|
+
Returns:
|
|
45
|
+
Database object
|
|
46
|
+
"""
|
|
47
|
+
pass
|
|
48
|
+
|
|
49
|
+
@abstractmethod
|
|
50
|
+
def delete_database(self, name: str, tenant: str = DEFAULT_TENANT) -> None:
|
|
51
|
+
"""
|
|
52
|
+
Delete database
|
|
53
|
+
|
|
54
|
+
Args:
|
|
55
|
+
name: database name
|
|
56
|
+
tenant: tenant name (for OceanBase)
|
|
57
|
+
"""
|
|
58
|
+
pass
|
|
59
|
+
|
|
60
|
+
@abstractmethod
|
|
61
|
+
def list_databases(
|
|
62
|
+
self,
|
|
63
|
+
limit: Optional[int] = None,
|
|
64
|
+
offset: Optional[int] = None,
|
|
65
|
+
tenant: str = DEFAULT_TENANT
|
|
66
|
+
) -> Sequence[Database]:
|
|
67
|
+
"""
|
|
68
|
+
List all databases
|
|
69
|
+
|
|
70
|
+
Args:
|
|
71
|
+
limit: maximum number of results to return
|
|
72
|
+
offset: number of results to skip
|
|
73
|
+
tenant: tenant name (for OceanBase)
|
|
74
|
+
|
|
75
|
+
Returns:
|
|
76
|
+
Sequence of Database objects
|
|
77
|
+
"""
|
|
78
|
+
pass
|
|
79
|
+
|
|
80
|
+
|
|
81
|
+
class _AdminClientProxy(AdminAPI):
|
|
82
|
+
"""
|
|
83
|
+
A lightweight facade that delegates all operations to the underlying ServerAPI (BaseClient).
|
|
84
|
+
The actual logic is in the specific client implementations (Embedded/Server/OceanBase).
|
|
85
|
+
|
|
86
|
+
Note: This is an internal class. Users should use the AdminClient() factory function.
|
|
87
|
+
"""
|
|
88
|
+
|
|
89
|
+
_server: "BaseClient"
|
|
90
|
+
|
|
91
|
+
def __init__(self, server: "BaseClient") -> None:
|
|
92
|
+
"""
|
|
93
|
+
Initialize admin client with a server implementation
|
|
94
|
+
|
|
95
|
+
Args:
|
|
96
|
+
server: The underlying client that implements the actual logic
|
|
97
|
+
"""
|
|
98
|
+
self._server = server
|
|
99
|
+
|
|
100
|
+
def create_database(self, name: str, tenant: str = DEFAULT_TENANT) -> None:
|
|
101
|
+
"""Proxy to server implementation"""
|
|
102
|
+
return self._server.create_database(name=name, tenant=tenant)
|
|
103
|
+
|
|
104
|
+
def get_database(self, name: str, tenant: str = DEFAULT_TENANT) -> Database:
|
|
105
|
+
"""Proxy to server implementation"""
|
|
106
|
+
return self._server.get_database(name=name, tenant=tenant)
|
|
107
|
+
|
|
108
|
+
def delete_database(self, name: str, tenant: str = DEFAULT_TENANT) -> None:
|
|
109
|
+
"""Proxy to server implementation"""
|
|
110
|
+
return self._server.delete_database(name=name, tenant=tenant)
|
|
111
|
+
|
|
112
|
+
def list_databases(
|
|
113
|
+
self,
|
|
114
|
+
limit: Optional[int] = None,
|
|
115
|
+
offset: Optional[int] = None,
|
|
116
|
+
tenant: str = DEFAULT_TENANT
|
|
117
|
+
) -> Sequence[Database]:
|
|
118
|
+
"""Proxy to server implementation"""
|
|
119
|
+
return self._server.list_databases(limit=limit, offset=offset, tenant=tenant)
|
|
120
|
+
|
|
121
|
+
def __repr__(self):
|
|
122
|
+
return f"<AdminClient server={self._server}>"
|
|
123
|
+
|
|
124
|
+
def __enter__(self):
|
|
125
|
+
"""Context manager support - delegate to server"""
|
|
126
|
+
self._server.__enter__()
|
|
127
|
+
return self
|
|
128
|
+
|
|
129
|
+
def __exit__(self, exc_type, exc_val, exc_tb):
|
|
130
|
+
"""Context manager support - delegate to server"""
|
|
131
|
+
return self._server.__exit__(exc_type, exc_val, exc_tb)
|
|
132
|
+
|
|
133
|
+
|
|
134
|
+
class _ClientProxy:
|
|
135
|
+
"""
|
|
136
|
+
Internal client proxy for collection operations only.
|
|
137
|
+
Strictly separates collection management from database management.
|
|
138
|
+
|
|
139
|
+
Note: This is an internal class. Users should use the Client() factory function.
|
|
140
|
+
"""
|
|
141
|
+
|
|
142
|
+
_server: "BaseClient"
|
|
143
|
+
|
|
144
|
+
def __init__(self, server: "BaseClient") -> None:
|
|
145
|
+
"""
|
|
146
|
+
Initialize client with a server implementation
|
|
147
|
+
|
|
148
|
+
Args:
|
|
149
|
+
server: The underlying client that implements the actual logic
|
|
150
|
+
"""
|
|
151
|
+
self._server = server
|
|
152
|
+
|
|
153
|
+
def create_collection(
|
|
154
|
+
self,
|
|
155
|
+
name: str,
|
|
156
|
+
dimension: Optional[int] = None,
|
|
157
|
+
**kwargs
|
|
158
|
+
) -> "Collection":
|
|
159
|
+
"""Proxy to server implementation - collection operations only"""
|
|
160
|
+
return self._server.create_collection(name=name, dimension=dimension, **kwargs)
|
|
161
|
+
|
|
162
|
+
def get_collection(self, name: str) -> "Collection":
|
|
163
|
+
"""Proxy to server implementation - collection operations only"""
|
|
164
|
+
return self._server.get_collection(name=name)
|
|
165
|
+
|
|
166
|
+
def delete_collection(self, name: str) -> None:
|
|
167
|
+
"""Proxy to server implementation - collection operations only"""
|
|
168
|
+
return self._server.delete_collection(name=name)
|
|
169
|
+
|
|
170
|
+
def list_collections(self) -> List["Collection"]:
|
|
171
|
+
"""Proxy to server implementation - collection operations only"""
|
|
172
|
+
return self._server.list_collections()
|
|
173
|
+
|
|
174
|
+
def has_collection(self, name: str) -> bool:
|
|
175
|
+
"""Proxy to server implementation - collection operations only"""
|
|
176
|
+
return self._server.has_collection(name=name)
|
|
177
|
+
|
|
178
|
+
def get_or_create_collection(
|
|
179
|
+
self,
|
|
180
|
+
name: str,
|
|
181
|
+
dimension: Optional[int] = None,
|
|
182
|
+
**kwargs
|
|
183
|
+
) -> "Collection":
|
|
184
|
+
"""Proxy to server implementation - collection operations only"""
|
|
185
|
+
return self._server.get_or_create_collection(name=name, dimension=dimension, **kwargs)
|
|
186
|
+
|
|
187
|
+
def count_collection(self) -> int:
|
|
188
|
+
"""Proxy to server implementation - collection operations only"""
|
|
189
|
+
return self._server.count_collection()
|
|
190
|
+
|
|
191
|
+
def __repr__(self):
|
|
192
|
+
return f"<Client server={self._server}>"
|
|
193
|
+
|
|
194
|
+
def __enter__(self):
|
|
195
|
+
"""Context manager support - delegate to server"""
|
|
196
|
+
self._server.__enter__()
|
|
197
|
+
return self
|
|
198
|
+
|
|
199
|
+
def __exit__(self, exc_type, exc_val, exc_tb):
|
|
200
|
+
"""Context manager support - delegate to server"""
|
|
201
|
+
return self._server.__exit__(exc_type, exc_val, exc_tb)
|
|
202
|
+
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Base connection interface definition
|
|
3
|
+
"""
|
|
4
|
+
from abc import ABC, abstractmethod
|
|
5
|
+
from typing import Any
|
|
6
|
+
|
|
7
|
+
class _Transaction:
|
|
8
|
+
"""
|
|
9
|
+
Internal transaction object
|
|
10
|
+
"""
|
|
11
|
+
def __init__(self, connection: "BaseConnection"):
|
|
12
|
+
self._connection = connection
|
|
13
|
+
|
|
14
|
+
def __enter__(self):
|
|
15
|
+
return self
|
|
16
|
+
|
|
17
|
+
def __exit__(self, exc_type, exc_val, exc_tb):
|
|
18
|
+
if exc_type is not None:
|
|
19
|
+
self._connection.rollback()
|
|
20
|
+
else:
|
|
21
|
+
self._connection.commit()
|
|
22
|
+
|
|
23
|
+
class BaseConnection(ABC):
|
|
24
|
+
"""
|
|
25
|
+
Abstract base class for connection management.
|
|
26
|
+
Defines unified connection interface for all clients.
|
|
27
|
+
"""
|
|
28
|
+
|
|
29
|
+
# ==================== Connection Management ====================
|
|
30
|
+
|
|
31
|
+
@abstractmethod
|
|
32
|
+
def _ensure_connection(self) -> Any:
|
|
33
|
+
"""Ensure connection is established (internal method)"""
|
|
34
|
+
pass
|
|
35
|
+
|
|
36
|
+
@abstractmethod
|
|
37
|
+
def is_connected(self) -> bool:
|
|
38
|
+
"""Check connection status"""
|
|
39
|
+
pass
|
|
40
|
+
|
|
41
|
+
@abstractmethod
|
|
42
|
+
def _cleanup(self):
|
|
43
|
+
"""Internal cleanup method to close connection and release resources"""
|
|
44
|
+
pass
|
|
45
|
+
|
|
46
|
+
@abstractmethod
|
|
47
|
+
def execute(self, sql: str) -> Any:
|
|
48
|
+
"""Execute SQL statement (basic functionality)"""
|
|
49
|
+
pass
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
@abstractmethod
|
|
53
|
+
def get_raw_connection(self) -> Any:
|
|
54
|
+
"""Get raw connection object"""
|
|
55
|
+
pass
|
|
56
|
+
|
|
57
|
+
@property
|
|
58
|
+
@abstractmethod
|
|
59
|
+
def mode(self) -> str:
|
|
60
|
+
"""Return client mode (e.g., 'SeekdbEmbeddedClient', 'SeekdbServerClient', 'OceanBaseServerClient')"""
|
|
61
|
+
pass
|
|
62
|
+
|
|
63
|
+
# ==================== Context Manager ====================
|
|
64
|
+
|
|
65
|
+
def __enter__(self):
|
|
66
|
+
"""Context manager support"""
|
|
67
|
+
return self
|
|
68
|
+
|
|
69
|
+
def __exit__(self, exc_type, exc_val, exc_tb):
|
|
70
|
+
"""Context manager support: automatic resource cleanup"""
|
|
71
|
+
self._cleanup()
|
|
72
|
+
|
|
73
|
+
def __del__(self):
|
|
74
|
+
"""Destructor: ensure connection is closed to prevent resource leaks"""
|
|
75
|
+
try:
|
|
76
|
+
if hasattr(self, '_connection') and self.is_connected():
|
|
77
|
+
self._cleanup()
|
|
78
|
+
except Exception:
|
|
79
|
+
# Ignore all exceptions in destructor
|
|
80
|
+
# Avoid issues during interpreter shutdown
|
|
81
|
+
pass
|
|
82
|
+
|