Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Appearance settings

Commit a2828b2

Browse filesBrowse files
hnwyllmmlongbingljwxxsc0529
authored
merge from master (#86)
<!-- Thank you for contributing to OceanBase! Please feel free to ping the maintainers for the review! --> ## Summary <!-- Please clearly and concisely describe the purpose of this pull request. If this pull request resolves an issue, please link it via "close #xxx" or "fix #xxx". --> merge from master <!-- This is an auto-generated comment: release notes by coderabbit.ai --> ## Summary by CodeRabbit * **New Features** * Added database type & version detection and a Version type exposed at package top-level. * RAG demo updated to hybrid search (vector + full-text) with multiple embedding modes and an interactive RAG interface. * **Documentation** * Updated RAG demo docs (English and Chinese) to reflect hybrid search and embedding modes. * Added a comprehensive PR review/report template. * **Tests** * Added tests for version detection, Version comparisons, and extensive collection retrieval scenarios. <sub>✏️ Tip: You can customize this high-level summary in your review settings.</sub> <!-- end of auto-generated comment: release notes by coderabbit.ai --> --------- Co-authored-by: longbingljw <longbing.ljw@oceanbase.com> Co-authored-by: zhouyh <xxsc0529@163.com> Co-authored-by: xxsc0529 <xxsc0529@users.noreply.github.com>
1 parent 4e34c47 commit a2828b2
Copy full SHA for a2828b2

File tree

Expand file treeCollapse file tree

6 files changed

+453
-5
lines changed
Open diff view settings
Filter options
Expand file treeCollapse file tree

6 files changed

+453
-5
lines changed
Open diff view settings
Collapse file

‎src/pyseekdb/__init__.py‎

Copy file name to clipboardExpand all lines: src/pyseekdb/__init__.py
+2Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,7 @@
5656
AdminAPI,
5757
AdminClient,
5858
Database,
59+
Version,
5960
)
6061
from .client.collection import Collection
6162
from .client.hybrid_search import (
@@ -105,5 +106,6 @@
105106
'METADATAS',
106107
'EMBEDDINGS_FIELD',
107108
'SCORES',
109+
'Version',
108110
]
109111

Collapse file

‎src/pyseekdb/client/__init__.py‎

Copy file name to clipboardExpand all lines: src/pyseekdb/client/__init__.py
+2Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@
3737
from .client_seekdb_embedded import SeekdbEmbeddedClient
3838
from .client_seekdb_server import RemoteServerClient
3939
from .database import Database
40+
from .version import Version
4041
from .admin_client import AdminAPI, _AdminClientProxy, _ClientProxy
4142
from .hybrid_search import (
4243
HybridSearch,
@@ -79,6 +80,7 @@
7980
'METADATAS',
8081
'EMBEDDINGS_FIELD',
8182
'SCORES',
83+
'Version',
8284
]
8385

8486
def Client(
Collapse file

‎src/pyseekdb/client/client_base.py‎

Copy file name to clipboardExpand all lines: src/pyseekdb/client/client_base.py
+103-4Lines changed: 103 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,9 @@
77
import struct
88
from abc import ABC, abstractmethod
99
from typing import List, Optional, Sequence, Dict, Any, Union, TYPE_CHECKING, Tuple, Callable
10+
11+
if TYPE_CHECKING:
12+
from .version import Version
1013
from dataclasses import dataclass
1114
from pymysql.converters import escape_string
1215

@@ -197,20 +200,116 @@ def has_collection(self, name: str) -> bool:
197200
class BaseClient(BaseConnection, AdminAPI):
198201
"""
199202
Abstract base class for all clients.
200-
203+
201204
Design Pattern:
202205
1. Provides public collection management methods (create_collection, get_collection, etc.)
203206
2. Defines internal operation interfaces (_collection_* methods) called by Collection objects
204207
3. Subclasses implement all abstract methods to provide specific business logic
205-
208+
206209
Benefits of this design:
207210
- Collection object interface is unified regardless of which client created it
208211
- Different clients can have completely different underlying implementations (SQL/gRPC/REST)
209212
- Easy to extend with new client types
210-
213+
211214
Inherits connection management from BaseConnection and database operations from AdminAPI.
212215
"""
213-
216+
217+
# ==================== Database Type Detection ====================
218+
219+
def detect_db_type_and_version(self) -> Tuple[str, "Version"]:
220+
"""
221+
Detect database type and version.
222+
223+
Works for all three modes: seekdb-embedded, seekdb-server, and oceanbase.
224+
Version detection is case-insensitive for seekdb.
225+
226+
Returns:
227+
(db_type, version): ("seekdb", Version("x.x.x.x")) or ("oceanbase", Version("x.x.x.x"))
228+
229+
Raises:
230+
ValueError: If unable to detect database type or version
231+
232+
Examples:
233+
>>> db_type, version = client.detect_db_type_and_version()
234+
>>> version > Version("1.0.0.0")
235+
True
236+
"""
237+
from .version import Version
238+
239+
def _get_value(result, key: str) -> Optional[str]:
240+
"""Extract value from query result"""
241+
if not result or len(result) == 0:
242+
return None
243+
row = result[0]
244+
if isinstance(row, dict):
245+
value = row.get(key, '')
246+
elif isinstance(row, (tuple, list)) and len(row) > 0:
247+
value = row[0]
248+
else:
249+
value = str(row)
250+
return str(value).strip() if value else None
251+
252+
def _query(sql: str, key: str) -> Optional[str]:
253+
"""Execute SQL and return value"""
254+
try:
255+
result = self._execute(sql)
256+
return _get_value(result, key)
257+
except Exception as e:
258+
logger.debug(f"Failed to execute {sql}: {e}")
259+
return None
260+
261+
def _extract_seekdb_version(version_str: str) -> Optional[str]:
262+
"""Extract version from seekdb version string (case-insensitive)"""
263+
# Use case-insensitive pattern matching
264+
for pattern in [r'seekdb[-\s]v?(\d+\.\d+\.\d+\.\d+)', r'seekdb[-\s]v?(\d+\.\d+\.\d+)']:
265+
match = re.search(pattern, version_str, re.IGNORECASE)
266+
if match:
267+
return match.group(1)
268+
return None
269+
270+
# Ensure connection is established
271+
self._ensure_connection()
272+
273+
# Check version() for seekdb (case-insensitive)
274+
version_result = _query("SELECT version() as version", "version")
275+
if version_result and re.search(r'seekdb', version_result, re.IGNORECASE):
276+
seekdb_version_str = _extract_seekdb_version(version_result)
277+
if seekdb_version_str:
278+
return ("seekdb", Version(seekdb_version_str))
279+
else:
280+
raise ValueError(f"Detected seekdb in version string, but failed to extract version: {version_result}")
281+
282+
# Query ob_version() for OceanBase
283+
ob_version_str = _query("SELECT ob_version() as ob_version", "ob_version")
284+
if ob_version_str:
285+
# Try to parse OceanBase version (may have different format)
286+
try:
287+
return ("oceanbase", Version(ob_version_str))
288+
except ValueError:
289+
# If OceanBase version doesn't match standard format, try to extract numeric parts
290+
291+
parts = re.findall(r'\d+', ob_version_str)
292+
if len(parts) >= 3:
293+
# Take first 3 or 4 parts
294+
version_str = '.'.join(parts[:4] if len(parts) >= 4 else parts[:3])
295+
return ("oceanbase", Version(version_str))
296+
else:
297+
# Fallback: return as-is but wrap in Version with minimal format
298+
# This handles edge cases where version format is unusual
299+
raise ValueError(f"Unable to parse OceanBase version: {ob_version_str}")
300+
301+
# Truncate potentially verbose or sensitive database responses in error message
302+
def _truncate(val, length=20):
303+
if val is None:
304+
return "None"
305+
val_str = str(val)
306+
return val_str[:length] + ("..." if len(val_str) > length else "")
307+
308+
raise ValueError(
309+
f"Unable to detect database type. version()={_truncate(version_result)}, "
310+
f"ob_version()={_truncate(ob_version_str)}"
311+
)
312+
214313
# ==================== Collection Management (User-facing) ====================
215314

216315
def create_collection(
Collapse file

‎src/pyseekdb/client/client_seekdb_server.py‎

Copy file name to clipboardExpand all lines: src/pyseekdb/client/client_seekdb_server.py
+1-1Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
Supports both seekdb Server and OceanBase Server
44
"""
55
import logging
6-
from typing import Any, List, Optional, Sequence, Dict, Union
6+
from typing import Any, Optional, Sequence, Tuple
77

88
import pymysql
99
from pymysql.cursors import DictCursor
Collapse file

‎src/pyseekdb/client/version.py‎

Copy file name to clipboard
+122Lines changed: 122 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,122 @@
1+
"""
2+
Version class for representing and comparing database versions
3+
"""
4+
from typing import List, Optional, Tuple
5+
6+
7+
class Version:
8+
"""
9+
Represents a version number with support for comparison operations.
10+
11+
Supports versions in format: x.x.x or x.x.x.x (3 or 4 numeric parts)
12+
13+
Examples:
14+
>>> v1 = Version("1.0.1.0")
15+
>>> v2 = Version("1.0.0.1")
16+
>>> v1 > v2
17+
True
18+
19+
>>> v1 = Version("1.2.3")
20+
>>> v2 = Version("1.2.4")
21+
>>> v1 < v2
22+
True
23+
"""
24+
25+
def __init__(self, version_str: str):
26+
"""
27+
Initialize Version from string.
28+
29+
Args:
30+
version_str: Version string in format x.x.x or x.x.x.x
31+
32+
Raises:
33+
ValueError: If version string format is invalid
34+
"""
35+
if not version_str:
36+
raise ValueError("Version string cannot be empty")
37+
38+
parts = version_str.split('.')
39+
if len(parts) not in (3, 4):
40+
raise ValueError(
41+
f"Version format should be x.x.x or x.x.x.x (3 or 4 numeric parts), got: {version_str}"
42+
)
43+
44+
try:
45+
self._parts = [int(part) for part in parts]
46+
except ValueError as e:
47+
raise ValueError(
48+
f"Version parts must be numeric, got: {version_str}"
49+
) from e
50+
51+
# Normalize to 4 parts for comparison (pad with 0 if needed)
52+
if len(self._parts) == 3:
53+
self._parts.append(0)
54+
55+
@property
56+
def parts(self) -> Tuple[int, int, int, int]:
57+
"""Get version parts as tuple"""
58+
return tuple(self._parts)
59+
60+
@property
61+
def major(self) -> int:
62+
"""Get major version number"""
63+
return self._parts[0]
64+
65+
@property
66+
def minor(self) -> int:
67+
"""Get minor version number"""
68+
return self._parts[1]
69+
70+
@property
71+
def patch(self) -> int:
72+
"""Get patch version number"""
73+
return self._parts[2]
74+
75+
@property
76+
def build(self) -> int:
77+
"""Get build version number (0 if not specified)"""
78+
return self._parts[3]
79+
80+
def __str__(self) -> str:
81+
"""Convert to string representation"""
82+
# Always return full version (preserve all parts including trailing .0)
83+
return '.'.join(str(p) for p in self._parts)
84+
85+
def __repr__(self) -> str:
86+
"""Representation for debugging"""
87+
return f"Version('{self}')"
88+
89+
def __eq__(self, other) -> bool:
90+
"""Check equality"""
91+
if not isinstance(other, Version):
92+
return NotImplemented
93+
return self._parts == other._parts
94+
95+
def __lt__(self, other) -> bool:
96+
"""Check if less than"""
97+
if not isinstance(other, Version):
98+
return NotImplemented
99+
return self._parts < other._parts
100+
101+
def __le__(self, other) -> bool:
102+
"""Check if less than or equal"""
103+
if not isinstance(other, Version):
104+
return NotImplemented
105+
return self._parts <= other._parts
106+
107+
def __gt__(self, other) -> bool:
108+
"""Check if greater than"""
109+
if not isinstance(other, Version):
110+
return NotImplemented
111+
return self._parts > other._parts
112+
113+
def __ge__(self, other) -> bool:
114+
"""Check if greater than or equal"""
115+
if not isinstance(other, Version):
116+
return NotImplemented
117+
return self._parts >= other._parts
118+
119+
def __hash__(self) -> int:
120+
"""Hash for use in sets and dicts"""
121+
return hash(tuple(self._parts))
122+

0 commit comments

Comments
0 (0)
Morty Proxy This is a proxified and sanitized view of the page, visit original site.