pyobvector 0.2.12__py3-none-any.whl → 0.2.14__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.
- pyobvector/client/ob_vec_client.py +59 -0
- pyobvector/client/ob_vec_json_table_client.py +3 -0
- pyobvector/schema/array.py +41 -61
- pyobvector/schema/reflection.py +4 -2
- {pyobvector-0.2.12.dist-info → pyobvector-0.2.14.dist-info}/METADATA +2 -2
- {pyobvector-0.2.12.dist-info → pyobvector-0.2.14.dist-info}/RECORD +8 -8
- {pyobvector-0.2.12.dist-info → pyobvector-0.2.14.dist-info}/LICENSE +0 -0
- {pyobvector-0.2.12.dist-info → pyobvector-0.2.14.dist-info}/WHEEL +0 -0
|
@@ -89,6 +89,21 @@ class ObVecClient:
|
|
|
89
89
|
message=ExceptionsMessage.ClusterVersionIsLow,
|
|
90
90
|
)
|
|
91
91
|
|
|
92
|
+
def refresh_metadata(self, tables: Optional[list[str]] = None):
|
|
93
|
+
"""Reload metadata from the database.
|
|
94
|
+
|
|
95
|
+
Args:
|
|
96
|
+
tables (Optional[list[str]]): names of the tables to refresh. If None, refresh all tables.
|
|
97
|
+
"""
|
|
98
|
+
if tables is not None:
|
|
99
|
+
for table_name in tables:
|
|
100
|
+
if table_name in self.metadata_obj.tables:
|
|
101
|
+
self.metadata_obj.remove(Table(table_name, self.metadata_obj))
|
|
102
|
+
self.metadata_obj.reflect(bind=self.engine, only=tables, extend_existing=True)
|
|
103
|
+
else:
|
|
104
|
+
self.metadata_obj.clear()
|
|
105
|
+
self.metadata_obj.reflect(bind=self.engine, extend_existing=True)
|
|
106
|
+
|
|
92
107
|
def _insert_partition_hint_for_query_sql(self, sql: str, partition_hint: str):
|
|
93
108
|
from_index = sql.find("FROM")
|
|
94
109
|
assert from_index != -1
|
|
@@ -801,3 +816,47 @@ class ObVecClient:
|
|
|
801
816
|
with self.engine.connect() as conn:
|
|
802
817
|
with conn.begin():
|
|
803
818
|
return conn.execute(text(text_sql))
|
|
819
|
+
|
|
820
|
+
def add_columns(
|
|
821
|
+
self,
|
|
822
|
+
table_name: str,
|
|
823
|
+
columns: list[Column],
|
|
824
|
+
):
|
|
825
|
+
"""Add multiple columns to an existing table.
|
|
826
|
+
|
|
827
|
+
Args:
|
|
828
|
+
table_name (string): table name
|
|
829
|
+
columns (list[Column]): list of SQLAlchemy Column objects representing the new columns
|
|
830
|
+
"""
|
|
831
|
+
compiler = self.engine.dialect.ddl_compiler(self.engine.dialect, None)
|
|
832
|
+
column_specs = [compiler.get_column_specification(column) for column in columns]
|
|
833
|
+
columns_ddl = ", ".join(f"ADD COLUMN {spec}" for spec in column_specs)
|
|
834
|
+
|
|
835
|
+
with self.engine.connect() as conn:
|
|
836
|
+
with conn.begin():
|
|
837
|
+
conn.execute(
|
|
838
|
+
text(f"ALTER TABLE `{table_name}` {columns_ddl}")
|
|
839
|
+
)
|
|
840
|
+
|
|
841
|
+
self.refresh_metadata([table_name])
|
|
842
|
+
|
|
843
|
+
def drop_columns(
|
|
844
|
+
self,
|
|
845
|
+
table_name: str,
|
|
846
|
+
column_names: list[str],
|
|
847
|
+
):
|
|
848
|
+
"""Drop multiple columns from an existing table.
|
|
849
|
+
|
|
850
|
+
Args:
|
|
851
|
+
table_name (string): table name
|
|
852
|
+
column_names (list[str]): names of the columns to drop
|
|
853
|
+
"""
|
|
854
|
+
columns_ddl = ", ".join(f"DROP COLUMN `{name}`" for name in column_names)
|
|
855
|
+
|
|
856
|
+
with self.engine.connect() as conn:
|
|
857
|
+
with conn.begin():
|
|
858
|
+
conn.execute(
|
|
859
|
+
text(f"ALTER TABLE `{table_name}` {columns_ddl}")
|
|
860
|
+
)
|
|
861
|
+
|
|
862
|
+
self.refresh_metadata([table_name])
|
|
@@ -236,6 +236,7 @@ class ObVecJsonTableClient(ObVecClient):
|
|
|
236
236
|
raise ValueError("Table name duplicated")
|
|
237
237
|
|
|
238
238
|
session = self.session()
|
|
239
|
+
session.execute(text("SET @@session.autocommit=0"))
|
|
239
240
|
new_meta_cache_items = []
|
|
240
241
|
col_id = 16
|
|
241
242
|
for col_def in ast.find_all(exp.ColumnDef):
|
|
@@ -607,6 +608,7 @@ class ObVecJsonTableClient(ObVecClient):
|
|
|
607
608
|
raise ValueError(f"Table {jtable_name} does not exists")
|
|
608
609
|
|
|
609
610
|
session = self.session()
|
|
611
|
+
session.execute(text("SET @@session.autocommit=0"))
|
|
610
612
|
for action in ast.actions:
|
|
611
613
|
if isinstance(action, ChangeColumn):
|
|
612
614
|
self._handle_alter_jtable_change_column(
|
|
@@ -681,6 +683,7 @@ class ObVecJsonTableClient(ObVecClient):
|
|
|
681
683
|
raise ValueError(f"Invalid ast type {ast.this}")
|
|
682
684
|
|
|
683
685
|
session = self.session()
|
|
686
|
+
session.execute(text("SET @@session.autocommit=0"))
|
|
684
687
|
n_new_records = 0
|
|
685
688
|
for tuple in ast.expression.expressions:
|
|
686
689
|
expr_list = tuple.expressions
|
pyobvector/schema/array.py
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
"""ARRAY: An extended data type for SQLAlchemy"""
|
|
2
2
|
import json
|
|
3
|
-
from typing import Any, List, Optional, Sequence, Union
|
|
3
|
+
from typing import Any, List, Optional, Sequence, Union
|
|
4
4
|
|
|
5
5
|
from sqlalchemy.sql.type_api import TypeEngine
|
|
6
6
|
from sqlalchemy.types import UserDefinedType, String
|
|
@@ -10,7 +10,6 @@ class ARRAY(UserDefinedType):
|
|
|
10
10
|
"""ARRAY data type definition with support for up to 6 levels of nesting."""
|
|
11
11
|
cache_ok = True
|
|
12
12
|
_string = String()
|
|
13
|
-
_max_nesting_level = 6
|
|
14
13
|
|
|
15
14
|
def __init__(self, item_type: Union[TypeEngine, type]):
|
|
16
15
|
"""Construct an ARRAY.
|
|
@@ -18,25 +17,17 @@ class ARRAY(UserDefinedType):
|
|
|
18
17
|
Args:
|
|
19
18
|
item_type: The data type of items in this array. For nested arrays,
|
|
20
19
|
pass another ARRAY type.
|
|
21
|
-
|
|
22
|
-
Raises:
|
|
23
|
-
ValueError: If nesting level exceeds the maximum allowed level (6).
|
|
24
20
|
"""
|
|
25
21
|
super(UserDefinedType, self).__init__()
|
|
26
22
|
if isinstance(item_type, type):
|
|
27
23
|
item_type = item_type()
|
|
28
24
|
self.item_type = item_type
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
while isinstance(current_type, ARRAY):
|
|
36
|
-
level += 1
|
|
37
|
-
if level > self._max_nesting_level:
|
|
38
|
-
raise ValueError(f"Maximum nesting level of {self._max_nesting_level} exceeded")
|
|
39
|
-
current_type = current_type.item_type
|
|
25
|
+
if isinstance(item_type, ARRAY):
|
|
26
|
+
self.dim = item_type.dim + 1
|
|
27
|
+
else:
|
|
28
|
+
self.dim = 1
|
|
29
|
+
if self.dim > 6:
|
|
30
|
+
raise ValueError("Maximum nesting level of 6 exceeded")
|
|
40
31
|
|
|
41
32
|
def get_col_spec(self, **kw): # pylint: disable=unused-argument
|
|
42
33
|
"""Parse to array data type definition in text SQL."""
|
|
@@ -46,12 +37,33 @@ class ARRAY(UserDefinedType):
|
|
|
46
37
|
base_type = str(self.item_type)
|
|
47
38
|
return f"ARRAY({base_type})"
|
|
48
39
|
|
|
40
|
+
def _get_list_depth(self, value: Any) -> int:
|
|
41
|
+
if not isinstance(value, list):
|
|
42
|
+
return 0
|
|
43
|
+
max_depth = 0
|
|
44
|
+
for element in value:
|
|
45
|
+
current_depth = self._get_list_depth(element)
|
|
46
|
+
if current_depth > max_depth:
|
|
47
|
+
max_depth = current_depth
|
|
48
|
+
return 1 + max_depth
|
|
49
|
+
|
|
50
|
+
def _validate_dimension(self, value: list[Any]):
|
|
51
|
+
arr_depth = self._get_list_depth(value)
|
|
52
|
+
assert arr_depth == self.dim, "Array dimension mismatch, expected {}, got {}".format(self.dim, arr_depth)
|
|
53
|
+
|
|
49
54
|
def bind_processor(self, dialect):
|
|
50
|
-
|
|
55
|
+
item_type = self.item_type
|
|
56
|
+
while isinstance(item_type, ARRAY):
|
|
57
|
+
item_type = item_type.item_type
|
|
51
58
|
|
|
52
|
-
|
|
59
|
+
item_proc = item_type.dialect_impl(dialect).bind_processor(dialect)
|
|
60
|
+
|
|
61
|
+
def process(value: Optional[Sequence[Any] | str]) -> Optional[str]:
|
|
53
62
|
if value is None:
|
|
54
63
|
return None
|
|
64
|
+
if isinstance(value, str):
|
|
65
|
+
self._validate_dimension(json.loads(value))
|
|
66
|
+
return value
|
|
55
67
|
|
|
56
68
|
def convert(val):
|
|
57
69
|
if isinstance(val, (list, tuple)):
|
|
@@ -61,12 +73,17 @@ class ARRAY(UserDefinedType):
|
|
|
61
73
|
return val
|
|
62
74
|
|
|
63
75
|
processed = convert(value)
|
|
76
|
+
self._validate_dimension(processed)
|
|
64
77
|
return json.dumps(processed)
|
|
65
78
|
|
|
66
79
|
return process
|
|
67
80
|
|
|
68
81
|
def result_processor(self, dialect, coltype):
|
|
69
|
-
|
|
82
|
+
item_type = self.item_type
|
|
83
|
+
while isinstance(item_type, ARRAY):
|
|
84
|
+
item_type = item_type.item_type
|
|
85
|
+
|
|
86
|
+
item_proc = item_type.dialect_impl(dialect).result_processor(dialect, coltype)
|
|
70
87
|
|
|
71
88
|
def process(value: Optional[str]) -> Optional[List[Any]]:
|
|
72
89
|
if value is None:
|
|
@@ -85,7 +102,11 @@ class ARRAY(UserDefinedType):
|
|
|
85
102
|
return process
|
|
86
103
|
|
|
87
104
|
def literal_processor(self, dialect):
|
|
88
|
-
|
|
105
|
+
item_type = self.item_type
|
|
106
|
+
while isinstance(item_type, ARRAY):
|
|
107
|
+
item_type = item_type.item_type
|
|
108
|
+
|
|
109
|
+
item_proc = item_type.dialect_impl(dialect).literal_processor(dialect)
|
|
89
110
|
|
|
90
111
|
def process(value: Sequence[Any]) -> str:
|
|
91
112
|
def convert(val):
|
|
@@ -99,44 +120,3 @@ class ARRAY(UserDefinedType):
|
|
|
99
120
|
return json.dumps(processed)
|
|
100
121
|
|
|
101
122
|
return process
|
|
102
|
-
|
|
103
|
-
def __repr__(self):
|
|
104
|
-
"""Return a string representation of the array type."""
|
|
105
|
-
current_type = self.item_type
|
|
106
|
-
nesting_level = 1
|
|
107
|
-
base_type = current_type
|
|
108
|
-
|
|
109
|
-
# Find the innermost type and count nesting level
|
|
110
|
-
while isinstance(current_type, ARRAY):
|
|
111
|
-
nesting_level += 1
|
|
112
|
-
current_type = current_type.item_type
|
|
113
|
-
if not isinstance(current_type, ARRAY):
|
|
114
|
-
base_type = current_type
|
|
115
|
-
|
|
116
|
-
return f"{nesting_level}D_Array({base_type})"
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
def nested_array(dim: int) -> Type[ARRAY]:
|
|
120
|
-
"""Create a nested array type class with specified dimensions.
|
|
121
|
-
|
|
122
|
-
Args:
|
|
123
|
-
dim: The number of dimensions for the array type (1-6)
|
|
124
|
-
|
|
125
|
-
Returns:
|
|
126
|
-
A class type that can be instantiated with an item_type to create a nested array
|
|
127
|
-
|
|
128
|
-
Raises:
|
|
129
|
-
ValueError: If dim is not between 1 and 6
|
|
130
|
-
"""
|
|
131
|
-
if not 1 <= dim <= 6:
|
|
132
|
-
raise ValueError("Dimension must be between 1 and 6")
|
|
133
|
-
|
|
134
|
-
class ArrayType(ARRAY):
|
|
135
|
-
def __init__(self, item_type: Union[TypeEngine, type]):
|
|
136
|
-
nested_type = item_type
|
|
137
|
-
for _ in range(dim - 1):
|
|
138
|
-
nested_type = ARRAY(nested_type)
|
|
139
|
-
super().__init__(nested_type)
|
|
140
|
-
|
|
141
|
-
ArrayType.__name__ = f"{dim}D_Array"
|
|
142
|
-
return ArrayType
|
pyobvector/schema/reflection.py
CHANGED
|
@@ -3,7 +3,7 @@ import re
|
|
|
3
3
|
import logging
|
|
4
4
|
from sqlalchemy.dialects.mysql.reflection import MySQLTableDefinitionParser, _re_compile, cleanup_text
|
|
5
5
|
|
|
6
|
-
from pyobvector.schema.array import
|
|
6
|
+
from pyobvector.schema.array import ARRAY
|
|
7
7
|
|
|
8
8
|
logger = logging.getLogger(__name__)
|
|
9
9
|
|
|
@@ -100,7 +100,9 @@ class OceanBaseTableDefinitionParser(MySQLTableDefinitionParser):
|
|
|
100
100
|
item_type_args = [int(v) for v in self._re_csv_int.findall(item_type_arg)]
|
|
101
101
|
|
|
102
102
|
nested_level = coltype_with_args.lower().count('array')
|
|
103
|
-
type_instance =
|
|
103
|
+
type_instance = item_type(*item_type_args)
|
|
104
|
+
for _ in range(nested_level):
|
|
105
|
+
type_instance = ARRAY(type_instance)
|
|
104
106
|
|
|
105
107
|
col_kw = {}
|
|
106
108
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.3
|
|
2
2
|
Name: pyobvector
|
|
3
|
-
Version: 0.2.
|
|
3
|
+
Version: 0.2.14
|
|
4
4
|
Summary: A python SDK for OceanBase Vector Store, based on SQLAlchemy, compatible with Milvus API.
|
|
5
5
|
Author: shanhaikang.shk
|
|
6
6
|
Author-email: shanhaikang.shk@oceanbase.com
|
|
@@ -36,7 +36,7 @@ poetry install
|
|
|
36
36
|
- install with pip:
|
|
37
37
|
|
|
38
38
|
```shell
|
|
39
|
-
pip install pyobvector==0.2.
|
|
39
|
+
pip install pyobvector==0.2.14
|
|
40
40
|
```
|
|
41
41
|
|
|
42
42
|
## Build Doc
|
|
@@ -6,8 +6,8 @@ pyobvector/client/exceptions.py,sha256=CAsTHR9juYleRjYIa4bqk_lw14h8daBvChKoU0o19
|
|
|
6
6
|
pyobvector/client/fts_index_param.py,sha256=hMCjA3Aecnt0uQQT6UQGTIIqdPk1M4gX4-zREDQygLs,1139
|
|
7
7
|
pyobvector/client/index_param.py,sha256=3gXi66Ey1PO9x5_61CrH7DmPb496kviBQI5NT7nfbGc,6309
|
|
8
8
|
pyobvector/client/milvus_like_client.py,sha256=CpPo6mkGE8iNFpKGBFof3h7E1VTzy1DAPGlFM9F_s8g,26373
|
|
9
|
-
pyobvector/client/ob_vec_client.py,sha256=
|
|
10
|
-
pyobvector/client/ob_vec_json_table_client.py,sha256=
|
|
9
|
+
pyobvector/client/ob_vec_client.py,sha256=XRbsf9wT6obnbJTBV-xlseXBrkvMhkfmzis-gQKD6Os,31566
|
|
10
|
+
pyobvector/client/ob_vec_json_table_client.py,sha256=rq80AfqAKhosLcrBFROAoINVSkr-48xlRH91Jt4pEwA,39246
|
|
11
11
|
pyobvector/client/partitions.py,sha256=Bxwr5yVNlXwZc7SXBC03NeqL9giy4Fe6S2qZdHD8xGw,15621
|
|
12
12
|
pyobvector/client/schema_type.py,sha256=u1LJsr1o9lxv2b_6KYu77RciFa1R_Qk69k_WT30x6BU,1582
|
|
13
13
|
pyobvector/json_table/__init__.py,sha256=X5MmK3f10oyJleUUFZJFeunMEfzmf6P1f_7094b-FZc,554
|
|
@@ -15,14 +15,14 @@ pyobvector/json_table/json_value_returning_func.py,sha256=NWSV2zhe2-1KhIprQaFqOH
|
|
|
15
15
|
pyobvector/json_table/oceanbase_dialect.py,sha256=lxpbWBQdK18LWXLmGyk_-ODv6VfnwGLHbcpsQMElOUo,4480
|
|
16
16
|
pyobvector/json_table/virtual_data_type.py,sha256=uQh6ZQ0UbwpVO9TFegGeu4E8bXW7rdLHAXFQJdiEjLs,3467
|
|
17
17
|
pyobvector/schema/__init__.py,sha256=EU8NH8Q-L05sFBGKPV6yIBUeh5f3awTkArdBJ7d4CvQ,2271
|
|
18
|
-
pyobvector/schema/array.py,sha256=
|
|
18
|
+
pyobvector/schema/array.py,sha256=WDWLZbCdu8stK8wlGWfKUjkhWifS8vbsfYUEEJsQOlQ,4163
|
|
19
19
|
pyobvector/schema/dialect.py,sha256=mdRjn3roztCkk6RXbaB0Wn1uhT2BPS2y18MwL6wW-jo,1840
|
|
20
20
|
pyobvector/schema/full_text_index.py,sha256=ohQX8uTPdRswEJONuN5A-bNv203d0N0b2BsJ7etx71g,2071
|
|
21
21
|
pyobvector/schema/geo_srid_point.py,sha256=RwEoCgGTmXDc0le1B2E3mZudtqiFdMf2M0Va1ocmVSY,1210
|
|
22
22
|
pyobvector/schema/gis_func.py,sha256=u7bqaB5qIylW8GvRdglLQL2H1SheQZNnAqgZrOGyrks,3118
|
|
23
23
|
pyobvector/schema/match_against_func.py,sha256=ExTQJvAXHaZwBo1Sjy6IlnF1nF6D9xGUsF4f7zaP8Q0,1336
|
|
24
24
|
pyobvector/schema/ob_table.py,sha256=wlb6Oo9LG-sr8XnG_wbX1Qi5CgnS0XUzNL5qTdsncoY,392
|
|
25
|
-
pyobvector/schema/reflection.py,sha256=
|
|
25
|
+
pyobvector/schema/reflection.py,sha256=GD49G_yh0uau-SbwJA4FiO53o72a-Mmd0EDRGvRiQ6U,5805
|
|
26
26
|
pyobvector/schema/replace_stmt.py,sha256=FtGLXHz6DwzD0FOZPn1EZgXdbHZu-K9HIHS02rZqYrE,560
|
|
27
27
|
pyobvector/schema/vec_dist_func.py,sha256=4GAWSrhFNDYooBpbBg604wDrByPrewp46Y4VeoDxV7Y,2986
|
|
28
28
|
pyobvector/schema/vector.py,sha256=dFKfPcTOto0jNxVjhvDmJM7Q4wwp6Z-HcZ3K6oZxUMc,1120
|
|
@@ -30,7 +30,7 @@ pyobvector/schema/vector_index.py,sha256=aNtrEBUclc4s6QuqCZpu3Hj3OdjyhbWgtLiJzo6
|
|
|
30
30
|
pyobvector/util/__init__.py,sha256=D9EgRDlcMSDhY3uI__vnCl45Or75dOXMWSval5P5fqs,251
|
|
31
31
|
pyobvector/util/ob_version.py,sha256=ZIySam8q_MCiwctAiAHPB4GdAzGQiXEo1wVkc9IOTDU,1539
|
|
32
32
|
pyobvector/util/vector.py,sha256=xyM-NuOyd78K7P3kinqyWvLIzEbf9c-4TKn_QVF7qgw,2265
|
|
33
|
-
pyobvector-0.2.
|
|
34
|
-
pyobvector-0.2.
|
|
35
|
-
pyobvector-0.2.
|
|
36
|
-
pyobvector-0.2.
|
|
33
|
+
pyobvector-0.2.14.dist-info/LICENSE,sha256=WNHhf_5RCaeuKWyq_K39vmp9F28LxKsB4SpomwSZ2L0,11357
|
|
34
|
+
pyobvector-0.2.14.dist-info/METADATA,sha256=dN3uAhB49nACFUiiQAwTYfSM0BgfUMxfaPHlQJTH4UA,6659
|
|
35
|
+
pyobvector-0.2.14.dist-info/WHEEL,sha256=b4K_helf-jlQoXBBETfwnf4B04YC67LOev0jo4fX5m8,88
|
|
36
|
+
pyobvector-0.2.14.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|