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,226 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Server mode client - based on pymysql
|
|
3
|
+
"""
|
|
4
|
+
import logging
|
|
5
|
+
from typing import Any, List, Optional, Sequence, Dict, Union
|
|
6
|
+
|
|
7
|
+
import pymysql
|
|
8
|
+
from pymysql.cursors import DictCursor
|
|
9
|
+
|
|
10
|
+
from .client_base import BaseClient
|
|
11
|
+
from .collection import Collection
|
|
12
|
+
from .database import Database
|
|
13
|
+
from .admin_client import DEFAULT_TENANT
|
|
14
|
+
from .query_result import QueryResult, QueryResultItem
|
|
15
|
+
|
|
16
|
+
logger = logging.getLogger(__name__)
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
class SeekdbServerClient(BaseClient):
|
|
20
|
+
"""SeekDB server mode client (connecting via pymysql, lazy loading)"""
|
|
21
|
+
|
|
22
|
+
def __init__(
|
|
23
|
+
self,
|
|
24
|
+
host: str = "localhost",
|
|
25
|
+
port: int = 2882,
|
|
26
|
+
database: str = "test",
|
|
27
|
+
user: str = "root",
|
|
28
|
+
password: str = "",
|
|
29
|
+
charset: str = "utf8mb4",
|
|
30
|
+
**kwargs
|
|
31
|
+
):
|
|
32
|
+
"""
|
|
33
|
+
Initialize server mode client (no immediate connection)
|
|
34
|
+
|
|
35
|
+
Args:
|
|
36
|
+
host: server address
|
|
37
|
+
port: server port
|
|
38
|
+
database: database name
|
|
39
|
+
user: username
|
|
40
|
+
password: password
|
|
41
|
+
charset: charset
|
|
42
|
+
"""
|
|
43
|
+
self.host = host
|
|
44
|
+
self.port = port
|
|
45
|
+
self.database = database
|
|
46
|
+
self.user = user
|
|
47
|
+
self.password = password
|
|
48
|
+
self.charset = charset
|
|
49
|
+
self.kwargs = kwargs
|
|
50
|
+
self._connection = None
|
|
51
|
+
|
|
52
|
+
logger.info(
|
|
53
|
+
f"Initialize SeekdbServerClient: {self.user}@{self.host}:{self.port}/{self.database}"
|
|
54
|
+
)
|
|
55
|
+
|
|
56
|
+
# ==================== Connection Management ====================
|
|
57
|
+
|
|
58
|
+
def _ensure_connection(self) -> pymysql.Connection:
|
|
59
|
+
"""Ensure connection is established (internal method)"""
|
|
60
|
+
if self._connection is None or not self._connection.open:
|
|
61
|
+
self._connection = pymysql.connect(
|
|
62
|
+
host=self.host,
|
|
63
|
+
port=self.port,
|
|
64
|
+
user=self.user,
|
|
65
|
+
password=self.password,
|
|
66
|
+
database=self.database,
|
|
67
|
+
charset=self.charset,
|
|
68
|
+
cursorclass=DictCursor,
|
|
69
|
+
**self.kwargs
|
|
70
|
+
)
|
|
71
|
+
logger.info(f"✅ Connected to server: {self.host}:{self.port}/{self.database}")
|
|
72
|
+
|
|
73
|
+
return self._connection
|
|
74
|
+
|
|
75
|
+
def _cleanup(self):
|
|
76
|
+
"""Internal cleanup method: close connection)"""
|
|
77
|
+
if self._connection is not None:
|
|
78
|
+
self._connection.close()
|
|
79
|
+
self._connection = None
|
|
80
|
+
logger.info("Connection closed")
|
|
81
|
+
|
|
82
|
+
def is_connected(self) -> bool:
|
|
83
|
+
"""Check connection status"""
|
|
84
|
+
return self._connection is not None and self._connection.open
|
|
85
|
+
|
|
86
|
+
def execute(self, sql: str) -> Any:
|
|
87
|
+
conn = self._ensure_connection()
|
|
88
|
+
|
|
89
|
+
with conn.cursor() as cursor:
|
|
90
|
+
cursor.execute(sql)
|
|
91
|
+
|
|
92
|
+
sql_upper = sql.strip().upper()
|
|
93
|
+
if (sql_upper.startswith('SELECT') or
|
|
94
|
+
sql_upper.startswith('SHOW') or
|
|
95
|
+
sql_upper.startswith('DESCRIBE') or
|
|
96
|
+
sql_upper.startswith('DESC')):
|
|
97
|
+
return cursor.fetchall()
|
|
98
|
+
|
|
99
|
+
conn.commit()
|
|
100
|
+
return cursor
|
|
101
|
+
|
|
102
|
+
def get_raw_connection(self) -> pymysql.Connection:
|
|
103
|
+
"""Get raw connection object"""
|
|
104
|
+
return self._ensure_connection()
|
|
105
|
+
|
|
106
|
+
@property
|
|
107
|
+
def mode(self) -> str:
|
|
108
|
+
return "SeekdbServerClient"
|
|
109
|
+
|
|
110
|
+
# ==================== Collection Management (framework) ====================
|
|
111
|
+
|
|
112
|
+
# create_collection is inherited from BaseClient - no override needed
|
|
113
|
+
# get_collection is inherited from BaseClient - no override needed
|
|
114
|
+
# delete_collection is inherited from BaseClient - no override needed
|
|
115
|
+
# list_collections is inherited from BaseClient - no override needed
|
|
116
|
+
# has_collection is inherited from BaseClient - no override needed
|
|
117
|
+
|
|
118
|
+
# ==================== Collection Internal Operations ====================
|
|
119
|
+
# These methods are called by Collection objects
|
|
120
|
+
|
|
121
|
+
# -------------------- DML Operations --------------------
|
|
122
|
+
# _collection_add is inherited from BaseClient
|
|
123
|
+
# _collection_update is inherited from BaseClient
|
|
124
|
+
# _collection_upsert is inherited from BaseClient
|
|
125
|
+
# _collection_delete is inherited from BaseClient
|
|
126
|
+
|
|
127
|
+
# -------------------- DQL Operations --------------------
|
|
128
|
+
# Note: _collection_query() and _collection_get() use base class implementation
|
|
129
|
+
|
|
130
|
+
# _collection_hybrid_search is inherited from BaseClient
|
|
131
|
+
|
|
132
|
+
# -------------------- Collection Info --------------------
|
|
133
|
+
|
|
134
|
+
# _collection_count is inherited from BaseClient - no override needed
|
|
135
|
+
|
|
136
|
+
# ==================== Database Management ====================
|
|
137
|
+
|
|
138
|
+
def create_database(self, name: str, tenant: str = DEFAULT_TENANT) -> None:
|
|
139
|
+
"""
|
|
140
|
+
Create database (tenant parameter ignored for server mode)
|
|
141
|
+
|
|
142
|
+
Args:
|
|
143
|
+
name: database name
|
|
144
|
+
tenant: ignored for server mode (no tenant concept)
|
|
145
|
+
"""
|
|
146
|
+
logger.info(f"Creating database: {name}")
|
|
147
|
+
sql = f"CREATE DATABASE IF NOT EXISTS `{name}`"
|
|
148
|
+
self.execute(sql)
|
|
149
|
+
logger.info(f"✅ Database created: {name}")
|
|
150
|
+
|
|
151
|
+
def get_database(self, name: str, tenant: str = DEFAULT_TENANT) -> Database:
|
|
152
|
+
"""
|
|
153
|
+
Get database object (tenant parameter ignored for server mode)
|
|
154
|
+
|
|
155
|
+
Args:
|
|
156
|
+
name: database name
|
|
157
|
+
tenant: ignored for server mode (no tenant concept)
|
|
158
|
+
"""
|
|
159
|
+
logger.info(f"Getting database: {name}")
|
|
160
|
+
sql = f"SELECT SCHEMA_NAME, DEFAULT_CHARACTER_SET_NAME, DEFAULT_COLLATION_NAME FROM information_schema.SCHEMATA WHERE SCHEMA_NAME = '{name}'"
|
|
161
|
+
result = self.execute(sql)
|
|
162
|
+
|
|
163
|
+
if not result:
|
|
164
|
+
raise ValueError(f"Database not found: {name}")
|
|
165
|
+
|
|
166
|
+
row = result[0]
|
|
167
|
+
return Database(
|
|
168
|
+
name=row['SCHEMA_NAME'],
|
|
169
|
+
tenant=None, # No tenant concept in server mode
|
|
170
|
+
charset=row['DEFAULT_CHARACTER_SET_NAME'],
|
|
171
|
+
collation=row['DEFAULT_COLLATION_NAME']
|
|
172
|
+
)
|
|
173
|
+
|
|
174
|
+
def delete_database(self, name: str, tenant: str = DEFAULT_TENANT) -> None:
|
|
175
|
+
"""
|
|
176
|
+
Delete database (tenant parameter ignored for server mode)
|
|
177
|
+
|
|
178
|
+
Args:
|
|
179
|
+
name: database name
|
|
180
|
+
tenant: ignored for server mode (no tenant concept)
|
|
181
|
+
"""
|
|
182
|
+
logger.info(f"Deleting database: {name}")
|
|
183
|
+
sql = f"DROP DATABASE IF EXISTS `{name}`"
|
|
184
|
+
self.execute(sql)
|
|
185
|
+
logger.info(f"✅ Database deleted: {name}")
|
|
186
|
+
|
|
187
|
+
def list_databases(
|
|
188
|
+
self,
|
|
189
|
+
limit: Optional[int] = None,
|
|
190
|
+
offset: Optional[int] = None,
|
|
191
|
+
tenant: str = DEFAULT_TENANT
|
|
192
|
+
) -> Sequence[Database]:
|
|
193
|
+
"""
|
|
194
|
+
List all databases (tenant parameter ignored for server mode)
|
|
195
|
+
|
|
196
|
+
Args:
|
|
197
|
+
limit: maximum number of results to return
|
|
198
|
+
offset: number of results to skip
|
|
199
|
+
tenant: ignored for server mode (no tenant concept)
|
|
200
|
+
"""
|
|
201
|
+
logger.info("Listing databases")
|
|
202
|
+
sql = "SELECT SCHEMA_NAME, DEFAULT_CHARACTER_SET_NAME, DEFAULT_COLLATION_NAME FROM information_schema.SCHEMATA"
|
|
203
|
+
|
|
204
|
+
if limit is not None:
|
|
205
|
+
if offset is not None:
|
|
206
|
+
sql += f" LIMIT {offset}, {limit}"
|
|
207
|
+
else:
|
|
208
|
+
sql += f" LIMIT {limit}"
|
|
209
|
+
|
|
210
|
+
result = self.execute(sql)
|
|
211
|
+
|
|
212
|
+
databases = []
|
|
213
|
+
for row in result:
|
|
214
|
+
databases.append(Database(
|
|
215
|
+
name=row['SCHEMA_NAME'],
|
|
216
|
+
tenant=None, # No tenant concept in server mode
|
|
217
|
+
charset=row['DEFAULT_CHARACTER_SET_NAME'],
|
|
218
|
+
collation=row['DEFAULT_COLLATION_NAME']
|
|
219
|
+
))
|
|
220
|
+
|
|
221
|
+
logger.info(f"✅ Found {len(databases)} databases")
|
|
222
|
+
return databases
|
|
223
|
+
|
|
224
|
+
def __repr__(self):
|
|
225
|
+
status = "connected" if self.is_connected() else "disconnected"
|
|
226
|
+
return f"<SeekdbServerClient {self.user}@{self.host}:{self.port}/{self.database} status={status}>"
|