matrixone-python-sdk 0.1.0__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.
- matrixone/__init__.py +155 -0
- matrixone/account.py +723 -0
- matrixone/async_client.py +3913 -0
- matrixone/async_metadata_manager.py +311 -0
- matrixone/async_orm.py +123 -0
- matrixone/async_vector_index_manager.py +633 -0
- matrixone/base_client.py +208 -0
- matrixone/client.py +4672 -0
- matrixone/config.py +452 -0
- matrixone/connection_hooks.py +286 -0
- matrixone/exceptions.py +89 -0
- matrixone/logger.py +782 -0
- matrixone/metadata.py +820 -0
- matrixone/moctl.py +219 -0
- matrixone/orm.py +2277 -0
- matrixone/pitr.py +646 -0
- matrixone/pubsub.py +771 -0
- matrixone/restore.py +411 -0
- matrixone/search_vector_index.py +1176 -0
- matrixone/snapshot.py +550 -0
- matrixone/sql_builder.py +844 -0
- matrixone/sqlalchemy_ext/__init__.py +161 -0
- matrixone/sqlalchemy_ext/adapters.py +163 -0
- matrixone/sqlalchemy_ext/dialect.py +534 -0
- matrixone/sqlalchemy_ext/fulltext_index.py +895 -0
- matrixone/sqlalchemy_ext/fulltext_search.py +1686 -0
- matrixone/sqlalchemy_ext/hnsw_config.py +194 -0
- matrixone/sqlalchemy_ext/ivf_config.py +252 -0
- matrixone/sqlalchemy_ext/table_builder.py +351 -0
- matrixone/sqlalchemy_ext/vector_index.py +1721 -0
- matrixone/sqlalchemy_ext/vector_type.py +948 -0
- matrixone/version.py +580 -0
- matrixone_python_sdk-0.1.0.dist-info/METADATA +706 -0
- matrixone_python_sdk-0.1.0.dist-info/RECORD +122 -0
- matrixone_python_sdk-0.1.0.dist-info/WHEEL +5 -0
- matrixone_python_sdk-0.1.0.dist-info/entry_points.txt +5 -0
- matrixone_python_sdk-0.1.0.dist-info/licenses/LICENSE +200 -0
- matrixone_python_sdk-0.1.0.dist-info/top_level.txt +2 -0
- tests/__init__.py +19 -0
- tests/offline/__init__.py +20 -0
- tests/offline/conftest.py +77 -0
- tests/offline/test_account.py +703 -0
- tests/offline/test_async_client_query_comprehensive.py +1218 -0
- tests/offline/test_basic.py +54 -0
- tests/offline/test_case_sensitivity.py +227 -0
- tests/offline/test_connection_hooks_offline.py +287 -0
- tests/offline/test_dialect_schema_handling.py +609 -0
- tests/offline/test_explain_methods.py +346 -0
- tests/offline/test_filter_logical_in.py +237 -0
- tests/offline/test_fulltext_search_comprehensive.py +795 -0
- tests/offline/test_ivf_config.py +249 -0
- tests/offline/test_join_methods.py +281 -0
- tests/offline/test_join_sqlalchemy_compatibility.py +276 -0
- tests/offline/test_logical_in_method.py +237 -0
- tests/offline/test_matrixone_version_parsing.py +264 -0
- tests/offline/test_metadata_offline.py +557 -0
- tests/offline/test_moctl.py +300 -0
- tests/offline/test_moctl_simple.py +251 -0
- tests/offline/test_model_support_offline.py +359 -0
- tests/offline/test_model_support_simple.py +225 -0
- tests/offline/test_pinecone_filter_offline.py +377 -0
- tests/offline/test_pitr.py +585 -0
- tests/offline/test_pubsub.py +712 -0
- tests/offline/test_query_update.py +283 -0
- tests/offline/test_restore.py +445 -0
- tests/offline/test_snapshot_comprehensive.py +384 -0
- tests/offline/test_sql_escaping_edge_cases.py +551 -0
- tests/offline/test_sqlalchemy_integration.py +382 -0
- tests/offline/test_sqlalchemy_vector_integration.py +434 -0
- tests/offline/test_table_builder.py +198 -0
- tests/offline/test_unified_filter.py +398 -0
- tests/offline/test_unified_transaction.py +495 -0
- tests/offline/test_vector_index.py +238 -0
- tests/offline/test_vector_operations.py +688 -0
- tests/offline/test_vector_type.py +174 -0
- tests/offline/test_version_core.py +328 -0
- tests/offline/test_version_management.py +372 -0
- tests/offline/test_version_standalone.py +652 -0
- tests/online/__init__.py +20 -0
- tests/online/conftest.py +216 -0
- tests/online/test_account_management.py +194 -0
- tests/online/test_advanced_features.py +344 -0
- tests/online/test_async_client_interfaces.py +330 -0
- tests/online/test_async_client_online.py +285 -0
- tests/online/test_async_model_insert_online.py +293 -0
- tests/online/test_async_orm_online.py +300 -0
- tests/online/test_async_simple_query_online.py +802 -0
- tests/online/test_async_transaction_simple_query.py +300 -0
- tests/online/test_basic_connection.py +130 -0
- tests/online/test_client_online.py +238 -0
- tests/online/test_config.py +90 -0
- tests/online/test_config_validation.py +123 -0
- tests/online/test_connection_hooks_new_online.py +217 -0
- tests/online/test_dialect_schema_handling_online.py +331 -0
- tests/online/test_filter_logical_in_online.py +374 -0
- tests/online/test_fulltext_comprehensive.py +1773 -0
- tests/online/test_fulltext_label_online.py +433 -0
- tests/online/test_fulltext_search_online.py +842 -0
- tests/online/test_ivf_stats_online.py +506 -0
- tests/online/test_logger_integration.py +311 -0
- tests/online/test_matrixone_query_orm.py +540 -0
- tests/online/test_metadata_online.py +579 -0
- tests/online/test_model_insert_online.py +255 -0
- tests/online/test_mysql_driver_validation.py +213 -0
- tests/online/test_orm_advanced_features.py +2022 -0
- tests/online/test_orm_cte_integration.py +269 -0
- tests/online/test_orm_online.py +270 -0
- tests/online/test_pinecone_filter.py +708 -0
- tests/online/test_pubsub_operations.py +352 -0
- tests/online/test_query_methods.py +225 -0
- tests/online/test_query_update_online.py +433 -0
- tests/online/test_search_vector_index.py +557 -0
- tests/online/test_simple_fulltext_online.py +915 -0
- tests/online/test_snapshot_comprehensive.py +998 -0
- tests/online/test_sqlalchemy_engine_integration.py +336 -0
- tests/online/test_sqlalchemy_integration.py +425 -0
- tests/online/test_transaction_contexts.py +1219 -0
- tests/online/test_transaction_insert_methods.py +356 -0
- tests/online/test_transaction_query_methods.py +288 -0
- tests/online/test_unified_filter_online.py +529 -0
- tests/online/test_vector_comprehensive.py +706 -0
- tests/online/test_version_management.py +291 -0
matrixone/metadata.py
ADDED
@@ -0,0 +1,820 @@
|
|
1
|
+
# Copyright 2021 - 2022 Matrix Origin
|
2
|
+
#
|
3
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
4
|
+
# you may not use this file except in compliance with the License.
|
5
|
+
# You may obtain a copy of the License at
|
6
|
+
#
|
7
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
8
|
+
#
|
9
|
+
# Unless required by applicable law or agreed to in writing, software
|
10
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
11
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
12
|
+
# See the License for the specific language governing permissions and
|
13
|
+
# limitations under the License.
|
14
|
+
|
15
|
+
"""
|
16
|
+
MatrixOne Metadata Operations
|
17
|
+
|
18
|
+
This module provides metadata scanning capabilities for MatrixOne tables,
|
19
|
+
allowing users to analyze table statistics, column information, and data distribution.
|
20
|
+
"""
|
21
|
+
|
22
|
+
from typing import Optional, List, Dict, Any, Union
|
23
|
+
from sqlalchemy.engine import Result
|
24
|
+
from dataclasses import dataclass
|
25
|
+
from enum import Enum
|
26
|
+
|
27
|
+
|
28
|
+
class MetadataColumn(Enum):
|
29
|
+
"""Enumeration of available metadata columns"""
|
30
|
+
|
31
|
+
COL_NAME = "col_name"
|
32
|
+
OBJECT_NAME = "object_name"
|
33
|
+
IS_HIDDEN = "is_hidden"
|
34
|
+
OBJ_LOC = "obj_loc"
|
35
|
+
CREATE_TS = "create_ts"
|
36
|
+
DELETE_TS = "delete_ts"
|
37
|
+
ROWS_CNT = "rows_cnt"
|
38
|
+
NULL_CNT = "null_cnt"
|
39
|
+
COMPRESS_SIZE = "compress_size"
|
40
|
+
ORIGIN_SIZE = "origin_size"
|
41
|
+
MIN = "min"
|
42
|
+
MAX = "max"
|
43
|
+
SUM = "sum"
|
44
|
+
|
45
|
+
|
46
|
+
@dataclass
|
47
|
+
class MetadataRow:
|
48
|
+
"""Structured representation of a metadata scan row"""
|
49
|
+
|
50
|
+
col_name: str
|
51
|
+
object_name: str
|
52
|
+
is_hidden: bool
|
53
|
+
obj_loc: str
|
54
|
+
create_ts: str
|
55
|
+
delete_ts: str
|
56
|
+
rows_cnt: int
|
57
|
+
null_cnt: int
|
58
|
+
compress_size: int
|
59
|
+
origin_size: int
|
60
|
+
min: Optional[Any] = None
|
61
|
+
max: Optional[Any] = None
|
62
|
+
sum: Optional[Any] = None
|
63
|
+
|
64
|
+
@classmethod
|
65
|
+
def from_sqlalchemy_row(cls, row) -> "MetadataRow":
|
66
|
+
"""Create MetadataRow from SQLAlchemy Row object"""
|
67
|
+
return cls(
|
68
|
+
col_name=row._mapping['col_name'],
|
69
|
+
object_name=row._mapping['object_name'],
|
70
|
+
is_hidden=row._mapping['is_hidden'] == 'true',
|
71
|
+
obj_loc=row._mapping['obj_loc'],
|
72
|
+
create_ts=row._mapping['create_ts'],
|
73
|
+
delete_ts=row._mapping['delete_ts'],
|
74
|
+
rows_cnt=row._mapping['rows_cnt'],
|
75
|
+
null_cnt=row._mapping['null_cnt'],
|
76
|
+
compress_size=row._mapping['compress_size'],
|
77
|
+
origin_size=row._mapping['origin_size'],
|
78
|
+
min=row._mapping.get('min'),
|
79
|
+
max=row._mapping.get('max'),
|
80
|
+
sum=row._mapping.get('sum'),
|
81
|
+
)
|
82
|
+
|
83
|
+
|
84
|
+
class BaseMetadataManager:
|
85
|
+
"""
|
86
|
+
Base metadata manager with shared SQL building logic.
|
87
|
+
"""
|
88
|
+
|
89
|
+
def _build_metadata_scan_sql(
|
90
|
+
self,
|
91
|
+
dbname: str,
|
92
|
+
tablename: str,
|
93
|
+
is_tombstone: Optional[bool] = None,
|
94
|
+
indexname: Optional[str] = None,
|
95
|
+
distinct_object_name: Optional[bool] = None,
|
96
|
+
) -> str:
|
97
|
+
"""
|
98
|
+
Build metadata_scan SQL query.
|
99
|
+
|
100
|
+
Args:
|
101
|
+
|
102
|
+
dbname: Database name
|
103
|
+
tablename: Table name
|
104
|
+
is_tombstone: Optional tombstone flag (True/False)
|
105
|
+
indexname: Optional index name
|
106
|
+
|
107
|
+
Returns:
|
108
|
+
|
109
|
+
SQL query string
|
110
|
+
"""
|
111
|
+
# Build the metadata_scan SQL query based on MatrixOne syntax
|
112
|
+
if is_tombstone and indexname:
|
113
|
+
# Format: "db_name.table_name.?index_name.#"
|
114
|
+
table_ref = f"{dbname}.{tablename}.?{indexname}.#"
|
115
|
+
elif is_tombstone:
|
116
|
+
# Format: "db_name.table_name.#"
|
117
|
+
table_ref = f"{dbname}.{tablename}.#"
|
118
|
+
elif indexname:
|
119
|
+
# Format: "db_name.table_name.?index_name"
|
120
|
+
table_ref = f"{dbname}.{tablename}.?{indexname}"
|
121
|
+
else:
|
122
|
+
# Format: "db_name.table_name"
|
123
|
+
table_ref = f"{dbname}.{tablename}"
|
124
|
+
|
125
|
+
# Column parameter - always use '*' for metadata_scan
|
126
|
+
column_param = "'*'"
|
127
|
+
|
128
|
+
# Build the SQL query
|
129
|
+
if distinct_object_name:
|
130
|
+
return f"SELECT DISTINCT(object_name) as object_name, * FROM metadata_scan('{table_ref}', {column_param}) g"
|
131
|
+
else:
|
132
|
+
return f"SELECT * FROM metadata_scan('{table_ref}', {column_param}) g"
|
133
|
+
|
134
|
+
def _process_scan_result(
|
135
|
+
self, result: Result, columns: Optional[Union[List[Union[MetadataColumn, str]], str]] = None
|
136
|
+
) -> Union[Result, List[MetadataRow], List[Dict[str, Any]]]:
|
137
|
+
"""
|
138
|
+
Process scan result based on columns parameter.
|
139
|
+
|
140
|
+
Args:
|
141
|
+
|
142
|
+
result: SQLAlchemy Result object
|
143
|
+
columns: Optional list of columns to return
|
144
|
+
|
145
|
+
Returns:
|
146
|
+
|
147
|
+
Processed result based on columns parameter
|
148
|
+
"""
|
149
|
+
# If columns are specified, return structured results
|
150
|
+
if columns is not None:
|
151
|
+
if columns == "*" or (isinstance(columns, list) and len(columns) == 0):
|
152
|
+
# Return all columns as structured data
|
153
|
+
return [MetadataRow.from_sqlalchemy_row(row) for row in result.fetchall()]
|
154
|
+
else:
|
155
|
+
# Return only specified columns as structured data
|
156
|
+
rows = []
|
157
|
+
for row in result.fetchall():
|
158
|
+
metadata_row = MetadataRow.from_sqlalchemy_row(row)
|
159
|
+
# Filter to only requested columns
|
160
|
+
filtered_row = {}
|
161
|
+
for col in columns:
|
162
|
+
col_name = col.value if isinstance(col, MetadataColumn) else col
|
163
|
+
if hasattr(metadata_row, col_name):
|
164
|
+
filtered_row[col_name] = getattr(metadata_row, col_name)
|
165
|
+
rows.append(filtered_row)
|
166
|
+
return rows
|
167
|
+
|
168
|
+
# Return raw SQLAlchemy Result
|
169
|
+
return result
|
170
|
+
|
171
|
+
def _format_size(self, size_bytes: int) -> str:
|
172
|
+
"""
|
173
|
+
Format size in bytes to human-readable format.
|
174
|
+
|
175
|
+
Args:
|
176
|
+
|
177
|
+
size_bytes: Size in bytes
|
178
|
+
|
179
|
+
Returns:
|
180
|
+
|
181
|
+
Formatted size string (e.g., "1.5 MB", "500 KB")
|
182
|
+
"""
|
183
|
+
if size_bytes == 0:
|
184
|
+
return "0 B"
|
185
|
+
|
186
|
+
units = ['B', 'KB', 'MB', 'GB', 'TB']
|
187
|
+
unit_index = 0
|
188
|
+
size = float(size_bytes)
|
189
|
+
|
190
|
+
while size >= 1024 and unit_index < len(units) - 1:
|
191
|
+
size /= 1024
|
192
|
+
unit_index += 1
|
193
|
+
|
194
|
+
# Format to 2 decimal places maximum
|
195
|
+
if size == int(size):
|
196
|
+
return f"{int(size)} {units[unit_index]}"
|
197
|
+
else:
|
198
|
+
return f"{size:.2f} {units[unit_index]}".rstrip('0').rstrip('.')
|
199
|
+
|
200
|
+
def _execute_sql(self, sql: str):
|
201
|
+
"""
|
202
|
+
Execute SQL query. Override in subclasses to use different executors.
|
203
|
+
|
204
|
+
Args:
|
205
|
+
|
206
|
+
sql: SQL query to execute
|
207
|
+
|
208
|
+
Returns:
|
209
|
+
|
210
|
+
SQLAlchemy Result object
|
211
|
+
"""
|
212
|
+
return self.client.execute(sql)
|
213
|
+
|
214
|
+
async def _execute_sql_async(self, sql: str):
|
215
|
+
"""
|
216
|
+
Execute SQL query asynchronously. Override in subclasses to use different executors.
|
217
|
+
|
218
|
+
Args:
|
219
|
+
|
220
|
+
sql: SQL query to execute
|
221
|
+
|
222
|
+
Returns:
|
223
|
+
|
224
|
+
SQLAlchemy Result object
|
225
|
+
"""
|
226
|
+
return await self.client.execute(sql)
|
227
|
+
|
228
|
+
def _get_table_brief_stats_logic(
|
229
|
+
self,
|
230
|
+
dbname: str,
|
231
|
+
tablename: str,
|
232
|
+
is_tombstone: Optional[bool] = None,
|
233
|
+
indexname: Optional[str] = None,
|
234
|
+
include_tombstone: bool = False,
|
235
|
+
include_indexes: Optional[List[str]] = None,
|
236
|
+
execute_func=None,
|
237
|
+
) -> Dict[str, Dict[str, Any]]:
|
238
|
+
"""
|
239
|
+
Common logic for get_table_brief_stats. Used by both sync and async versions.
|
240
|
+
|
241
|
+
Args:
|
242
|
+
|
243
|
+
dbname: Database name
|
244
|
+
tablename: Table name
|
245
|
+
is_tombstone: Optional tombstone flag (True/False)
|
246
|
+
indexname: Optional index name
|
247
|
+
include_tombstone: Whether to include tombstone statistics
|
248
|
+
include_indexes: List of index names to include
|
249
|
+
execute_func: Function to execute SQL (sync or async)
|
250
|
+
|
251
|
+
Returns:
|
252
|
+
|
253
|
+
Dictionary with brief statistics for table, tombstone, and indexes
|
254
|
+
"""
|
255
|
+
result = {}
|
256
|
+
|
257
|
+
# Get table statistics
|
258
|
+
table_sql = self._build_metadata_scan_sql(dbname, tablename, is_tombstone, indexname)
|
259
|
+
table_result = execute_func(table_sql)
|
260
|
+
table_rows = table_result.fetchall()
|
261
|
+
|
262
|
+
if table_rows:
|
263
|
+
# Aggregate table statistics
|
264
|
+
total_objects = len(set(row._mapping['object_name'] for row in table_rows))
|
265
|
+
total_original_size = sum(row._mapping.get('origin_size', 0) for row in table_rows)
|
266
|
+
total_compress_size = sum(row._mapping.get('compress_size', 0) for row in table_rows)
|
267
|
+
total_row_cnt = sum(row._mapping.get('rows_cnt', 0) for row in table_rows)
|
268
|
+
total_null_cnt = sum(row._mapping.get('null_cnt', 0) for row in table_rows)
|
269
|
+
|
270
|
+
result[tablename] = {
|
271
|
+
"total_objects": total_objects,
|
272
|
+
"original_size": self._format_size(total_original_size),
|
273
|
+
"compress_size": self._format_size(total_compress_size),
|
274
|
+
"row_cnt": total_row_cnt,
|
275
|
+
"null_cnt": total_null_cnt,
|
276
|
+
}
|
277
|
+
|
278
|
+
# Get tombstone statistics if requested
|
279
|
+
if include_tombstone:
|
280
|
+
tombstone_sql = self._build_metadata_scan_sql(dbname, tablename, is_tombstone=True)
|
281
|
+
tombstone_result = execute_func(tombstone_sql)
|
282
|
+
tombstone_rows = tombstone_result.fetchall()
|
283
|
+
|
284
|
+
if tombstone_rows:
|
285
|
+
total_objects = len(set(row._mapping['object_name'] for row in tombstone_rows))
|
286
|
+
total_original_size = sum(row._mapping.get('origin_size', 0) for row in tombstone_rows)
|
287
|
+
total_compress_size = sum(row._mapping.get('compress_size', 0) for row in tombstone_rows)
|
288
|
+
total_row_cnt = sum(row._mapping.get('rows_cnt', 0) for row in tombstone_rows)
|
289
|
+
total_null_cnt = sum(row._mapping.get('null_cnt', 0) for row in tombstone_rows)
|
290
|
+
|
291
|
+
result["tombstone"] = {
|
292
|
+
"total_objects": total_objects,
|
293
|
+
"original_size": self._format_size(total_original_size),
|
294
|
+
"compress_size": self._format_size(total_compress_size),
|
295
|
+
"row_cnt": total_row_cnt,
|
296
|
+
"null_cnt": total_null_cnt,
|
297
|
+
}
|
298
|
+
|
299
|
+
# Get index statistics if requested
|
300
|
+
if include_indexes:
|
301
|
+
for index_name in include_indexes:
|
302
|
+
index_sql = self._build_metadata_scan_sql(dbname, tablename, indexname=index_name)
|
303
|
+
index_result = execute_func(index_sql)
|
304
|
+
index_rows = index_result.fetchall()
|
305
|
+
|
306
|
+
if index_rows:
|
307
|
+
total_objects = len(set(row._mapping['object_name'] for row in index_rows))
|
308
|
+
total_original_size = sum(row._mapping.get('origin_size', 0) for row in index_rows)
|
309
|
+
total_compress_size = sum(row._mapping.get('compress_size', 0) for row in index_rows)
|
310
|
+
total_row_cnt = sum(row._mapping.get('rows_cnt', 0) for row in index_rows)
|
311
|
+
total_null_cnt = sum(row._mapping.get('null_cnt', 0) for row in index_rows)
|
312
|
+
|
313
|
+
result[index_name] = {
|
314
|
+
"total_objects": total_objects,
|
315
|
+
"original_size": self._format_size(total_original_size),
|
316
|
+
"compress_size": self._format_size(total_compress_size),
|
317
|
+
"row_cnt": total_row_cnt,
|
318
|
+
"null_cnt": total_null_cnt,
|
319
|
+
}
|
320
|
+
|
321
|
+
return result
|
322
|
+
|
323
|
+
async def _get_table_brief_stats_logic_async(
|
324
|
+
self,
|
325
|
+
dbname: str,
|
326
|
+
tablename: str,
|
327
|
+
is_tombstone: Optional[bool] = None,
|
328
|
+
indexname: Optional[str] = None,
|
329
|
+
include_tombstone: bool = False,
|
330
|
+
include_indexes: Optional[List[str]] = None,
|
331
|
+
execute_func=None,
|
332
|
+
) -> Dict[str, Dict[str, Any]]:
|
333
|
+
"""
|
334
|
+
Common async logic for get_table_brief_stats. Used by async versions.
|
335
|
+
|
336
|
+
Args:
|
337
|
+
|
338
|
+
dbname: Database name
|
339
|
+
tablename: Table name
|
340
|
+
is_tombstone: Optional tombstone flag (True/False)
|
341
|
+
indexname: Optional index name
|
342
|
+
include_tombstone: Whether to include tombstone statistics
|
343
|
+
include_indexes: List of index names to include
|
344
|
+
execute_func: Async function to execute SQL
|
345
|
+
|
346
|
+
Returns:
|
347
|
+
|
348
|
+
Dictionary with brief statistics for table, tombstone, and indexes
|
349
|
+
"""
|
350
|
+
result = {}
|
351
|
+
|
352
|
+
# Get table statistics
|
353
|
+
table_sql = self._build_metadata_scan_sql(dbname, tablename, is_tombstone, indexname)
|
354
|
+
table_result = await execute_func(table_sql)
|
355
|
+
table_rows = table_result.fetchall()
|
356
|
+
|
357
|
+
if table_rows:
|
358
|
+
# Aggregate table statistics
|
359
|
+
total_objects = len(set(row._mapping['object_name'] for row in table_rows))
|
360
|
+
total_original_size = sum(row._mapping.get('origin_size', 0) for row in table_rows)
|
361
|
+
total_compress_size = sum(row._mapping.get('compress_size', 0) for row in table_rows)
|
362
|
+
total_row_cnt = sum(row._mapping.get('rows_cnt', 0) for row in table_rows)
|
363
|
+
total_null_cnt = sum(row._mapping.get('null_cnt', 0) for row in table_rows)
|
364
|
+
|
365
|
+
result[tablename] = {
|
366
|
+
"total_objects": total_objects,
|
367
|
+
"original_size": self._format_size(total_original_size),
|
368
|
+
"compress_size": self._format_size(total_compress_size),
|
369
|
+
"row_cnt": total_row_cnt,
|
370
|
+
"null_cnt": total_null_cnt,
|
371
|
+
}
|
372
|
+
|
373
|
+
# Get tombstone statistics if requested
|
374
|
+
if include_tombstone:
|
375
|
+
tombstone_sql = self._build_metadata_scan_sql(dbname, tablename, is_tombstone=True)
|
376
|
+
tombstone_result = await execute_func(tombstone_sql)
|
377
|
+
tombstone_rows = tombstone_result.fetchall()
|
378
|
+
|
379
|
+
if tombstone_rows:
|
380
|
+
total_objects = len(set(row._mapping['object_name'] for row in tombstone_rows))
|
381
|
+
total_original_size = sum(row._mapping.get('origin_size', 0) for row in tombstone_rows)
|
382
|
+
total_compress_size = sum(row._mapping.get('compress_size', 0) for row in tombstone_rows)
|
383
|
+
total_row_cnt = sum(row._mapping.get('rows_cnt', 0) for row in tombstone_rows)
|
384
|
+
total_null_cnt = sum(row._mapping.get('null_cnt', 0) for row in tombstone_rows)
|
385
|
+
|
386
|
+
result["tombstone"] = {
|
387
|
+
"total_objects": total_objects,
|
388
|
+
"original_size": self._format_size(total_original_size),
|
389
|
+
"compress_size": self._format_size(total_compress_size),
|
390
|
+
"row_cnt": total_row_cnt,
|
391
|
+
"null_cnt": total_null_cnt,
|
392
|
+
}
|
393
|
+
|
394
|
+
# Get index statistics if requested
|
395
|
+
if include_indexes:
|
396
|
+
for index_name in include_indexes:
|
397
|
+
index_sql = self._build_metadata_scan_sql(dbname, tablename, indexname=index_name)
|
398
|
+
index_result = await execute_func(index_sql)
|
399
|
+
index_rows = index_result.fetchall()
|
400
|
+
|
401
|
+
if index_rows:
|
402
|
+
total_objects = len(set(row._mapping['object_name'] for row in index_rows))
|
403
|
+
total_original_size = sum(row._mapping.get('origin_size', 0) for row in index_rows)
|
404
|
+
total_compress_size = sum(row._mapping.get('compress_size', 0) for row in index_rows)
|
405
|
+
total_row_cnt = sum(row._mapping.get('rows_cnt', 0) for row in index_rows)
|
406
|
+
total_null_cnt = sum(row._mapping.get('null_cnt', 0) for row in index_rows)
|
407
|
+
|
408
|
+
result[index_name] = {
|
409
|
+
"total_objects": total_objects,
|
410
|
+
"original_size": self._format_size(total_original_size),
|
411
|
+
"compress_size": self._format_size(total_compress_size),
|
412
|
+
"row_cnt": total_row_cnt,
|
413
|
+
"null_cnt": total_null_cnt,
|
414
|
+
}
|
415
|
+
|
416
|
+
return result
|
417
|
+
|
418
|
+
async def _get_table_detail_stats_logic_async(
|
419
|
+
self,
|
420
|
+
dbname: str,
|
421
|
+
tablename: str,
|
422
|
+
is_tombstone: Optional[bool] = None,
|
423
|
+
indexname: Optional[str] = None,
|
424
|
+
include_tombstone: bool = False,
|
425
|
+
include_indexes: Optional[List[str]] = None,
|
426
|
+
execute_func=None,
|
427
|
+
) -> Dict[str, List[Dict[str, Any]]]:
|
428
|
+
"""
|
429
|
+
Common async logic for get_table_detail_stats. Used by async versions.
|
430
|
+
|
431
|
+
Args:
|
432
|
+
|
433
|
+
dbname: Database name
|
434
|
+
tablename: Table name
|
435
|
+
is_tombstone: Optional tombstone flag (True/False)
|
436
|
+
indexname: Optional index name
|
437
|
+
include_tombstone: Whether to include tombstone statistics
|
438
|
+
include_indexes: List of index names to include
|
439
|
+
execute_func: Async function to execute SQL
|
440
|
+
|
441
|
+
Returns:
|
442
|
+
|
443
|
+
Dictionary with detailed statistics for table, tombstone, and indexes
|
444
|
+
"""
|
445
|
+
result = {}
|
446
|
+
|
447
|
+
# Get table statistics
|
448
|
+
table_sql = self._build_metadata_scan_sql(dbname, tablename, is_tombstone, indexname)
|
449
|
+
table_result = await execute_func(table_sql)
|
450
|
+
table_rows = table_result.fetchall()
|
451
|
+
|
452
|
+
if table_rows:
|
453
|
+
# Convert to detailed format
|
454
|
+
table_details = []
|
455
|
+
for row in table_rows:
|
456
|
+
table_details.append(
|
457
|
+
{
|
458
|
+
"object_name": row._mapping['object_name'],
|
459
|
+
"create_ts": row._mapping['create_ts'],
|
460
|
+
"delete_ts": row._mapping['delete_ts'],
|
461
|
+
"row_cnt": row._mapping.get('rows_cnt', 0),
|
462
|
+
"null_cnt": row._mapping.get('null_cnt', 0),
|
463
|
+
"original_size": self._format_size(row._mapping.get('origin_size', 0)),
|
464
|
+
"compress_size": self._format_size(row._mapping.get('compress_size', 0)),
|
465
|
+
}
|
466
|
+
)
|
467
|
+
result[tablename] = table_details
|
468
|
+
|
469
|
+
# Get tombstone statistics if requested
|
470
|
+
if include_tombstone:
|
471
|
+
tombstone_sql = self._build_metadata_scan_sql(dbname, tablename, is_tombstone=True)
|
472
|
+
tombstone_result = await execute_func(tombstone_sql)
|
473
|
+
tombstone_rows = tombstone_result.fetchall()
|
474
|
+
|
475
|
+
if tombstone_rows:
|
476
|
+
tombstone_details = []
|
477
|
+
for row in tombstone_rows:
|
478
|
+
tombstone_details.append(
|
479
|
+
{
|
480
|
+
"object_name": row._mapping['object_name'],
|
481
|
+
"create_ts": row._mapping['create_ts'],
|
482
|
+
"delete_ts": row._mapping['delete_ts'],
|
483
|
+
"row_cnt": row._mapping.get('rows_cnt', 0),
|
484
|
+
"null_cnt": row._mapping.get('null_cnt', 0),
|
485
|
+
"original_size": self._format_size(row._mapping.get('origin_size', 0)),
|
486
|
+
"compress_size": self._format_size(row._mapping.get('compress_size', 0)),
|
487
|
+
}
|
488
|
+
)
|
489
|
+
result["tombstone"] = tombstone_details
|
490
|
+
|
491
|
+
# Get index statistics if requested
|
492
|
+
if include_indexes:
|
493
|
+
for index_name in include_indexes:
|
494
|
+
index_sql = self._build_metadata_scan_sql(dbname, tablename, indexname=index_name)
|
495
|
+
index_result = await execute_func(index_sql)
|
496
|
+
index_rows = index_result.fetchall()
|
497
|
+
|
498
|
+
if index_rows:
|
499
|
+
index_details = []
|
500
|
+
for row in index_rows:
|
501
|
+
index_details.append(
|
502
|
+
{
|
503
|
+
"object_name": row._mapping['object_name'],
|
504
|
+
"create_ts": row._mapping['create_ts'],
|
505
|
+
"delete_ts": row._mapping['delete_ts'],
|
506
|
+
"row_cnt": row._mapping.get('rows_cnt', 0),
|
507
|
+
"null_cnt": row._mapping.get('null_cnt', 0),
|
508
|
+
"original_size": self._format_size(row._mapping.get('origin_size', 0)),
|
509
|
+
"compress_size": self._format_size(row._mapping.get('compress_size', 0)),
|
510
|
+
}
|
511
|
+
)
|
512
|
+
result[index_name] = index_details
|
513
|
+
|
514
|
+
return result
|
515
|
+
|
516
|
+
def get_table_brief_stats(
|
517
|
+
self,
|
518
|
+
dbname: str,
|
519
|
+
tablename: str,
|
520
|
+
is_tombstone: Optional[bool] = None,
|
521
|
+
indexname: Optional[str] = None,
|
522
|
+
include_tombstone: bool = False,
|
523
|
+
include_indexes: Optional[List[str]] = None,
|
524
|
+
) -> Dict[str, Dict[str, Any]]:
|
525
|
+
"""
|
526
|
+
Get brief statistics for a table, tombstone, and indexes.
|
527
|
+
|
528
|
+
Args:
|
529
|
+
|
530
|
+
dbname: Database name
|
531
|
+
tablename: Table name
|
532
|
+
is_tombstone: Optional tombstone flag (True/False)
|
533
|
+
indexname: Optional index name
|
534
|
+
include_tombstone: Whether to include tombstone statistics
|
535
|
+
include_indexes: List of index names to include
|
536
|
+
|
537
|
+
Returns:
|
538
|
+
|
539
|
+
Dictionary with brief statistics for table, tombstone, and indexes
|
540
|
+
"""
|
541
|
+
return self._get_table_brief_stats_logic(
|
542
|
+
dbname, tablename, is_tombstone, indexname, include_tombstone, include_indexes, self._execute_sql
|
543
|
+
)
|
544
|
+
|
545
|
+
def _get_table_detail_stats_logic(
|
546
|
+
self,
|
547
|
+
dbname: str,
|
548
|
+
tablename: str,
|
549
|
+
is_tombstone: Optional[bool] = None,
|
550
|
+
indexname: Optional[str] = None,
|
551
|
+
include_tombstone: bool = False,
|
552
|
+
include_indexes: Optional[List[str]] = None,
|
553
|
+
execute_func=None,
|
554
|
+
) -> Dict[str, List[Dict[str, Any]]]:
|
555
|
+
"""
|
556
|
+
Common logic for get_table_detail_stats. Used by both sync and async versions.
|
557
|
+
|
558
|
+
Args:
|
559
|
+
|
560
|
+
dbname: Database name
|
561
|
+
tablename: Table name
|
562
|
+
is_tombstone: Optional tombstone flag (True/False)
|
563
|
+
indexname: Optional index name
|
564
|
+
include_tombstone: Whether to include tombstone statistics
|
565
|
+
include_indexes: List of index names to include
|
566
|
+
execute_func: Function to execute SQL (sync or async)
|
567
|
+
|
568
|
+
Returns:
|
569
|
+
|
570
|
+
Dictionary with detailed statistics for table, tombstone, and indexes
|
571
|
+
"""
|
572
|
+
result = {}
|
573
|
+
|
574
|
+
# Get table statistics
|
575
|
+
table_sql = self._build_metadata_scan_sql(dbname, tablename, is_tombstone, indexname)
|
576
|
+
table_result = execute_func(table_sql)
|
577
|
+
table_rows = table_result.fetchall()
|
578
|
+
|
579
|
+
if table_rows:
|
580
|
+
# Convert to detailed format
|
581
|
+
table_details = []
|
582
|
+
for row in table_rows:
|
583
|
+
table_details.append(
|
584
|
+
{
|
585
|
+
"object_name": row._mapping['object_name'],
|
586
|
+
"create_ts": row._mapping['create_ts'],
|
587
|
+
"delete_ts": row._mapping['delete_ts'],
|
588
|
+
"row_cnt": row._mapping.get('rows_cnt', 0),
|
589
|
+
"null_cnt": row._mapping.get('null_cnt', 0),
|
590
|
+
"original_size": self._format_size(row._mapping.get('origin_size', 0)),
|
591
|
+
"compress_size": self._format_size(row._mapping.get('compress_size', 0)),
|
592
|
+
}
|
593
|
+
)
|
594
|
+
result[tablename] = table_details
|
595
|
+
|
596
|
+
# Get tombstone statistics if requested
|
597
|
+
if include_tombstone:
|
598
|
+
tombstone_sql = self._build_metadata_scan_sql(dbname, tablename, is_tombstone=True)
|
599
|
+
tombstone_result = execute_func(tombstone_sql)
|
600
|
+
tombstone_rows = tombstone_result.fetchall()
|
601
|
+
|
602
|
+
if tombstone_rows:
|
603
|
+
tombstone_details = []
|
604
|
+
for row in tombstone_rows:
|
605
|
+
tombstone_details.append(
|
606
|
+
{
|
607
|
+
"object_name": row._mapping['object_name'],
|
608
|
+
"create_ts": row._mapping['create_ts'],
|
609
|
+
"delete_ts": row._mapping['delete_ts'],
|
610
|
+
"row_cnt": row._mapping.get('rows_cnt', 0),
|
611
|
+
"null_cnt": row._mapping.get('null_cnt', 0),
|
612
|
+
"original_size": self._format_size(row._mapping.get('origin_size', 0)),
|
613
|
+
"compress_size": self._format_size(row._mapping.get('compress_size', 0)),
|
614
|
+
}
|
615
|
+
)
|
616
|
+
result["tombstone"] = tombstone_details
|
617
|
+
|
618
|
+
# Get index statistics if requested
|
619
|
+
if include_indexes:
|
620
|
+
for index_name in include_indexes:
|
621
|
+
index_sql = self._build_metadata_scan_sql(dbname, tablename, indexname=index_name)
|
622
|
+
index_result = execute_func(index_sql)
|
623
|
+
index_rows = index_result.fetchall()
|
624
|
+
|
625
|
+
if index_rows:
|
626
|
+
index_details = []
|
627
|
+
for row in index_rows:
|
628
|
+
index_details.append(
|
629
|
+
{
|
630
|
+
"object_name": row._mapping['object_name'],
|
631
|
+
"create_ts": row._mapping['create_ts'],
|
632
|
+
"delete_ts": row._mapping['delete_ts'],
|
633
|
+
"row_cnt": row._mapping.get('rows_cnt', 0),
|
634
|
+
"null_cnt": row._mapping.get('null_cnt', 0),
|
635
|
+
"original_size": self._format_size(row._mapping.get('origin_size', 0)),
|
636
|
+
"compress_size": self._format_size(row._mapping.get('compress_size', 0)),
|
637
|
+
}
|
638
|
+
)
|
639
|
+
result[index_name] = index_details
|
640
|
+
|
641
|
+
return result
|
642
|
+
|
643
|
+
def get_table_detail_stats(
|
644
|
+
self,
|
645
|
+
dbname: str,
|
646
|
+
tablename: str,
|
647
|
+
is_tombstone: Optional[bool] = None,
|
648
|
+
indexname: Optional[str] = None,
|
649
|
+
include_tombstone: bool = False,
|
650
|
+
include_indexes: Optional[List[str]] = None,
|
651
|
+
) -> Dict[str, List[Dict[str, Any]]]:
|
652
|
+
"""
|
653
|
+
Get detailed statistics for a table, tombstone, and indexes.
|
654
|
+
|
655
|
+
Args:
|
656
|
+
|
657
|
+
dbname: Database name
|
658
|
+
tablename: Table name
|
659
|
+
is_tombstone: Optional tombstone flag (True/False)
|
660
|
+
indexname: Optional index name
|
661
|
+
include_tombstone: Whether to include tombstone statistics
|
662
|
+
include_indexes: List of index names to include
|
663
|
+
|
664
|
+
Returns:
|
665
|
+
|
666
|
+
Dictionary with detailed statistics for table, tombstone, and indexes
|
667
|
+
"""
|
668
|
+
return self._get_table_detail_stats_logic(
|
669
|
+
dbname, tablename, is_tombstone, indexname, include_tombstone, include_indexes, self._execute_sql
|
670
|
+
)
|
671
|
+
|
672
|
+
|
673
|
+
class MetadataManager(BaseMetadataManager):
|
674
|
+
"""
|
675
|
+
Metadata manager for MatrixOne table metadata operations.
|
676
|
+
|
677
|
+
Provides methods to scan table metadata including column statistics,
|
678
|
+
row counts, null counts, and data distribution information.
|
679
|
+
"""
|
680
|
+
|
681
|
+
def __init__(self, client):
|
682
|
+
"""
|
683
|
+
Initialize metadata manager.
|
684
|
+
|
685
|
+
Args:
|
686
|
+
|
687
|
+
client: MatrixOne client instance (Client or AsyncClient)
|
688
|
+
"""
|
689
|
+
self.client = client
|
690
|
+
|
691
|
+
def scan(
|
692
|
+
self,
|
693
|
+
dbname: str,
|
694
|
+
tablename: str,
|
695
|
+
is_tombstone: Optional[bool] = None,
|
696
|
+
indexname: Optional[str] = None,
|
697
|
+
columns: Optional[Union[List[Union[MetadataColumn, str]], str]] = None,
|
698
|
+
distinct_object_name: Optional[bool] = None,
|
699
|
+
) -> Union[Result, List[MetadataRow]]:
|
700
|
+
"""
|
701
|
+
Scan table metadata using metadata_scan function.
|
702
|
+
|
703
|
+
Args:
|
704
|
+
|
705
|
+
dbname: Database name
|
706
|
+
tablename: Table name
|
707
|
+
is_tombstone: Optional tombstone flag (True/False)
|
708
|
+
indexname: Optional index name
|
709
|
+
columns: Optional list of columns to return. Can be MetadataColumn enum values or strings.
|
710
|
+
If None, returns all columns as SQLAlchemy Result. If specified, returns List[MetadataRow].
|
711
|
+
distinct_object_name: Optional flag to return distinct object names only.
|
712
|
+
|
713
|
+
Returns:
|
714
|
+
|
715
|
+
If columns is None: SQLAlchemy Result object containing metadata scan results
|
716
|
+
If columns is specified: List[MetadataRow] containing structured metadata
|
717
|
+
|
718
|
+
Example:
|
719
|
+
|
720
|
+
```python
|
721
|
+
# Scan all columns of a table (returns SQLAlchemy Result)
|
722
|
+
result = client.metadata.scan("test_db", "users")
|
723
|
+
|
724
|
+
# Scan specific column
|
725
|
+
result = client.metadata.scan("test_db", "users", indexname="id")
|
726
|
+
|
727
|
+
# Scan with tombstone filter
|
728
|
+
result = client.metadata.scan("test_db", "users", is_tombstone=False)
|
729
|
+
|
730
|
+
# Scan tombstone objects
|
731
|
+
result = client.metadata.scan("test_db", "users", is_tombstone=True)
|
732
|
+
|
733
|
+
# Scan specific index
|
734
|
+
result = client.metadata.scan("test_db", "users", indexname="idx_name")
|
735
|
+
|
736
|
+
# Get structured results with specific columns
|
737
|
+
rows = client.metadata.scan("test_db", "users",
|
738
|
+
columns=[MetadataColumn.COL_NAME, MetadataColumn.ROWS_CNT])
|
739
|
+
for row in rows:
|
740
|
+
print(f"Column: {row.col_name}, Rows: {row.rows_cnt}")
|
741
|
+
|
742
|
+
# Get all structured results
|
743
|
+
rows = client.metadata.scan("test_db", "users", columns="*")
|
744
|
+
```
|
745
|
+
"""
|
746
|
+
# Build SQL query
|
747
|
+
sql = self._build_metadata_scan_sql(dbname, tablename, is_tombstone, indexname, distinct_object_name)
|
748
|
+
|
749
|
+
# Execute the query
|
750
|
+
result = self._execute_sql(sql)
|
751
|
+
|
752
|
+
# Process result based on columns parameter
|
753
|
+
return self._process_scan_result(result, columns)
|
754
|
+
|
755
|
+
|
756
|
+
class TransactionMetadataManager(BaseMetadataManager):
|
757
|
+
"""
|
758
|
+
Transaction metadata manager for MatrixOne table metadata operations within transactions.
|
759
|
+
|
760
|
+
Provides methods to scan table metadata including column statistics,
|
761
|
+
row counts, null counts, and data distribution information within a transaction context.
|
762
|
+
"""
|
763
|
+
|
764
|
+
def __init__(self, client, transaction_wrapper):
|
765
|
+
"""
|
766
|
+
Initialize transaction metadata manager.
|
767
|
+
|
768
|
+
Args:
|
769
|
+
|
770
|
+
client: MatrixOne client instance
|
771
|
+
transaction_wrapper: Transaction wrapper instance
|
772
|
+
"""
|
773
|
+
self.client = client
|
774
|
+
self.transaction_wrapper = transaction_wrapper
|
775
|
+
|
776
|
+
def _execute_sql(self, sql: str):
|
777
|
+
"""
|
778
|
+
Execute SQL query using transaction wrapper.
|
779
|
+
|
780
|
+
Args:
|
781
|
+
|
782
|
+
sql: SQL query to execute
|
783
|
+
|
784
|
+
Returns:
|
785
|
+
|
786
|
+
SQLAlchemy Result object
|
787
|
+
"""
|
788
|
+
return self.transaction_wrapper.execute(sql)
|
789
|
+
|
790
|
+
def scan(
|
791
|
+
self,
|
792
|
+
dbname: str,
|
793
|
+
tablename: str,
|
794
|
+
is_tombstone: Optional[bool] = None,
|
795
|
+
indexname: Optional[str] = None,
|
796
|
+
columns: Optional[Union[List[Union[MetadataColumn, str]], str]] = None,
|
797
|
+
distinct_object_name: Optional[bool] = None,
|
798
|
+
) -> Union[Result, List[MetadataRow]]:
|
799
|
+
"""
|
800
|
+
Scan table metadata using metadata_scan function within transaction.
|
801
|
+
|
802
|
+
Args:
|
803
|
+
|
804
|
+
dbname: Database name
|
805
|
+
tablename: Table name
|
806
|
+
is_tombstone: Optional tombstone flag (True/False)
|
807
|
+
indexname: Optional index name
|
808
|
+
|
809
|
+
Returns:
|
810
|
+
|
811
|
+
SQLAlchemy Result object containing metadata scan results
|
812
|
+
"""
|
813
|
+
# Build SQL query
|
814
|
+
sql = self._build_metadata_scan_sql(dbname, tablename, is_tombstone, indexname, distinct_object_name)
|
815
|
+
|
816
|
+
# Execute the query within transaction
|
817
|
+
result = self.transaction_wrapper.execute(sql)
|
818
|
+
|
819
|
+
# Process result based on columns parameter
|
820
|
+
return self._process_scan_result(result, columns)
|