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
@@ -0,0 +1,557 @@
|
|
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
|
+
Offline tests for MatrixOne metadata operations
|
17
|
+
"""
|
18
|
+
|
19
|
+
import asyncio
|
20
|
+
import unittest
|
21
|
+
from unittest.mock import Mock, patch, AsyncMock
|
22
|
+
from matrixone.metadata import MetadataManager, TransactionMetadataManager
|
23
|
+
from matrixone.async_metadata_manager import AsyncMetadataManager, AsyncTransactionMetadataManager
|
24
|
+
|
25
|
+
|
26
|
+
class TestMetadataManager(unittest.TestCase):
|
27
|
+
"""Test MetadataManager class"""
|
28
|
+
|
29
|
+
def setUp(self):
|
30
|
+
"""Set up test fixtures"""
|
31
|
+
self.mock_client = Mock()
|
32
|
+
self.metadata_manager = MetadataManager(self.mock_client)
|
33
|
+
|
34
|
+
def test_init(self):
|
35
|
+
"""Test MetadataManager initialization"""
|
36
|
+
self.assertEqual(self.metadata_manager.client, self.mock_client)
|
37
|
+
|
38
|
+
def test_scan_basic(self):
|
39
|
+
"""Test basic scan functionality"""
|
40
|
+
# Mock the execute method
|
41
|
+
mock_result = Mock()
|
42
|
+
mock_result.fetchall.return_value = [
|
43
|
+
Mock(_mapping={'col_name': 'id', 'rows_cnt': 100, 'null_cnt': 0, 'origin_size': 1000})
|
44
|
+
]
|
45
|
+
self.mock_client.execute.return_value = mock_result
|
46
|
+
|
47
|
+
# Test basic scan
|
48
|
+
result = self.metadata_manager.scan("test_db", "test_table")
|
49
|
+
|
50
|
+
# Verify SQL was called correctly
|
51
|
+
expected_sql = "SELECT * FROM metadata_scan('test_db.test_table', '*')"
|
52
|
+
self.mock_client.execute.assert_called_once()
|
53
|
+
call_args = self.mock_client.execute.call_args[0][0]
|
54
|
+
self.assertIn("metadata_scan('test_db.test_table', '*')", str(call_args))
|
55
|
+
|
56
|
+
def test_scan_with_index(self):
|
57
|
+
"""Test scan with index name"""
|
58
|
+
mock_result = Mock()
|
59
|
+
mock_result.fetchall.return_value = []
|
60
|
+
self.mock_client.execute.return_value = mock_result
|
61
|
+
|
62
|
+
# Test scan with index
|
63
|
+
result = self.metadata_manager.scan("test_db", "test_table", indexname="idx_name")
|
64
|
+
|
65
|
+
# Verify SQL includes index
|
66
|
+
call_args = self.mock_client.execute.call_args[0][0]
|
67
|
+
self.assertIn("metadata_scan('test_db.test_table.?idx_name', '*')", str(call_args))
|
68
|
+
|
69
|
+
def test_scan_with_tombstone(self):
|
70
|
+
"""Test scan with tombstone flag"""
|
71
|
+
mock_result = Mock()
|
72
|
+
mock_result.fetchall.return_value = []
|
73
|
+
self.mock_client.execute.return_value = mock_result
|
74
|
+
|
75
|
+
# Test scan with tombstone
|
76
|
+
result = self.metadata_manager.scan("test_db", "test_table", is_tombstone=True)
|
77
|
+
|
78
|
+
# Verify SQL includes tombstone
|
79
|
+
call_args = self.mock_client.execute.call_args[0][0]
|
80
|
+
self.assertIn("metadata_scan('test_db.test_table.#', '*')", str(call_args))
|
81
|
+
|
82
|
+
def test_scan_with_index_and_tombstone(self):
|
83
|
+
"""Test scan with both index and tombstone"""
|
84
|
+
mock_result = Mock()
|
85
|
+
mock_result.fetchall.return_value = []
|
86
|
+
self.mock_client.execute.return_value = mock_result
|
87
|
+
|
88
|
+
# Test scan with both index and tombstone
|
89
|
+
result = self.metadata_manager.scan("test_db", "test_table", indexname="idx_name", is_tombstone=True)
|
90
|
+
|
91
|
+
# Verify SQL includes both
|
92
|
+
call_args = self.mock_client.execute.call_args[0][0]
|
93
|
+
self.assertIn("metadata_scan('test_db.test_table.?idx_name.#', '*')", str(call_args))
|
94
|
+
|
95
|
+
def test_format_size(self):
|
96
|
+
"""Test _format_size method"""
|
97
|
+
# Test various size formats
|
98
|
+
self.assertEqual(self.metadata_manager._format_size(0), "0 B")
|
99
|
+
self.assertEqual(self.metadata_manager._format_size(1024), "1 KB")
|
100
|
+
self.assertEqual(self.metadata_manager._format_size(1536), "1.50 KB")
|
101
|
+
self.assertEqual(self.metadata_manager._format_size(1048576), "1 MB")
|
102
|
+
self.assertEqual(self.metadata_manager._format_size(1073741824), "1 GB")
|
103
|
+
self.assertEqual(self.metadata_manager._format_size(1073741824 * 2), "2 GB")
|
104
|
+
|
105
|
+
def test_get_table_brief_stats(self):
|
106
|
+
"""Test get_table_brief_stats method"""
|
107
|
+
# Mock the execute method
|
108
|
+
mock_result = Mock()
|
109
|
+
mock_result.fetchall.return_value = [
|
110
|
+
Mock(
|
111
|
+
_mapping={
|
112
|
+
'object_name': 'obj1',
|
113
|
+
'rows_cnt': 100,
|
114
|
+
'null_cnt': 5,
|
115
|
+
'origin_size': 1048576, # 1 MB
|
116
|
+
'compress_size': 524288, # 512 KB
|
117
|
+
}
|
118
|
+
),
|
119
|
+
Mock(
|
120
|
+
_mapping={
|
121
|
+
'object_name': 'obj2',
|
122
|
+
'rows_cnt': 50,
|
123
|
+
'null_cnt': 2,
|
124
|
+
'origin_size': 2097152, # 2 MB
|
125
|
+
'compress_size': 1048576, # 1 MB
|
126
|
+
}
|
127
|
+
),
|
128
|
+
]
|
129
|
+
self.mock_client.execute.return_value = mock_result
|
130
|
+
|
131
|
+
# Test basic brief stats
|
132
|
+
stats = self.metadata_manager.get_table_brief_stats("test_db", "test_table")
|
133
|
+
|
134
|
+
self.assertIn("test_table", stats)
|
135
|
+
table_stats = stats["test_table"]
|
136
|
+
self.assertEqual(table_stats["total_objects"], 2)
|
137
|
+
self.assertEqual(table_stats["original_size"], "3 MB")
|
138
|
+
self.assertEqual(table_stats["compress_size"], "1.50 MB")
|
139
|
+
self.assertEqual(table_stats["row_cnt"], 150)
|
140
|
+
self.assertEqual(table_stats["null_cnt"], 7)
|
141
|
+
|
142
|
+
def test_get_table_brief_stats_with_tombstone(self):
|
143
|
+
"""Test get_table_brief_stats with tombstone"""
|
144
|
+
# Mock multiple execute calls
|
145
|
+
mock_results = [
|
146
|
+
# Table results
|
147
|
+
Mock(
|
148
|
+
fetchall=Mock(
|
149
|
+
return_value=[
|
150
|
+
Mock(
|
151
|
+
_mapping={
|
152
|
+
'object_name': 'obj1',
|
153
|
+
'rows_cnt': 100,
|
154
|
+
'null_cnt': 5,
|
155
|
+
'origin_size': 1048576,
|
156
|
+
'compress_size': 524288,
|
157
|
+
}
|
158
|
+
)
|
159
|
+
]
|
160
|
+
)
|
161
|
+
),
|
162
|
+
# Tombstone results
|
163
|
+
Mock(
|
164
|
+
fetchall=Mock(
|
165
|
+
return_value=[
|
166
|
+
Mock(
|
167
|
+
_mapping={
|
168
|
+
'object_name': 'tomb1',
|
169
|
+
'rows_cnt': 20,
|
170
|
+
'null_cnt': 1,
|
171
|
+
'origin_size': 512000,
|
172
|
+
'compress_size': 256000,
|
173
|
+
}
|
174
|
+
)
|
175
|
+
]
|
176
|
+
)
|
177
|
+
),
|
178
|
+
]
|
179
|
+
self.mock_client.execute.side_effect = mock_results
|
180
|
+
|
181
|
+
# Test with tombstone
|
182
|
+
stats = self.metadata_manager.get_table_brief_stats("test_db", "test_table", include_tombstone=True)
|
183
|
+
|
184
|
+
self.assertIn("test_table", stats)
|
185
|
+
self.assertIn("tombstone", stats)
|
186
|
+
self.assertEqual(stats["tombstone"]["row_cnt"], 20)
|
187
|
+
|
188
|
+
def test_get_table_brief_stats_with_indexes(self):
|
189
|
+
"""Test get_table_brief_stats with indexes"""
|
190
|
+
# Mock multiple execute calls
|
191
|
+
mock_results = [
|
192
|
+
# Table results
|
193
|
+
Mock(
|
194
|
+
fetchall=Mock(
|
195
|
+
return_value=[
|
196
|
+
Mock(
|
197
|
+
_mapping={
|
198
|
+
'object_name': 'obj1',
|
199
|
+
'rows_cnt': 100,
|
200
|
+
'null_cnt': 5,
|
201
|
+
'origin_size': 1048576,
|
202
|
+
'compress_size': 524288,
|
203
|
+
}
|
204
|
+
)
|
205
|
+
]
|
206
|
+
)
|
207
|
+
),
|
208
|
+
# Index1 results
|
209
|
+
Mock(
|
210
|
+
fetchall=Mock(
|
211
|
+
return_value=[
|
212
|
+
Mock(
|
213
|
+
_mapping={
|
214
|
+
'object_name': 'idx1_obj1',
|
215
|
+
'rows_cnt': 100,
|
216
|
+
'null_cnt': 0,
|
217
|
+
'origin_size': 512000,
|
218
|
+
'compress_size': 256000,
|
219
|
+
}
|
220
|
+
)
|
221
|
+
]
|
222
|
+
)
|
223
|
+
),
|
224
|
+
# Index2 results
|
225
|
+
Mock(
|
226
|
+
fetchall=Mock(
|
227
|
+
return_value=[
|
228
|
+
Mock(
|
229
|
+
_mapping={
|
230
|
+
'object_name': 'idx2_obj1',
|
231
|
+
'rows_cnt': 100,
|
232
|
+
'null_cnt': 0,
|
233
|
+
'origin_size': 256000,
|
234
|
+
'compress_size': 128000,
|
235
|
+
}
|
236
|
+
)
|
237
|
+
]
|
238
|
+
)
|
239
|
+
),
|
240
|
+
]
|
241
|
+
self.mock_client.execute.side_effect = mock_results
|
242
|
+
|
243
|
+
# Test with indexes
|
244
|
+
stats = self.metadata_manager.get_table_brief_stats("test_db", "test_table", include_indexes=["idx1", "idx2"])
|
245
|
+
|
246
|
+
self.assertIn("test_table", stats)
|
247
|
+
self.assertIn("idx1", stats)
|
248
|
+
self.assertIn("idx2", stats)
|
249
|
+
self.assertEqual(stats["idx1"]["row_cnt"], 100)
|
250
|
+
self.assertEqual(stats["idx2"]["row_cnt"], 100)
|
251
|
+
|
252
|
+
def test_get_table_detail_stats(self):
|
253
|
+
"""Test get_table_detail_stats method"""
|
254
|
+
# Mock the execute method
|
255
|
+
mock_result = Mock()
|
256
|
+
mock_result.fetchall.return_value = [
|
257
|
+
Mock(
|
258
|
+
_mapping={
|
259
|
+
'object_name': 'obj1',
|
260
|
+
'create_ts': '2023-01-01 10:00:00',
|
261
|
+
'delete_ts': None,
|
262
|
+
'rows_cnt': 100,
|
263
|
+
'null_cnt': 5,
|
264
|
+
'origin_size': 1048576,
|
265
|
+
'compress_size': 524288,
|
266
|
+
}
|
267
|
+
),
|
268
|
+
Mock(
|
269
|
+
_mapping={
|
270
|
+
'object_name': 'obj2',
|
271
|
+
'create_ts': '2023-01-02 11:00:00',
|
272
|
+
'delete_ts': '2023-01-03 12:00:00',
|
273
|
+
'rows_cnt': 50,
|
274
|
+
'null_cnt': 2,
|
275
|
+
'origin_size': 2097152,
|
276
|
+
'compress_size': 1048576,
|
277
|
+
}
|
278
|
+
),
|
279
|
+
]
|
280
|
+
self.mock_client.execute.return_value = mock_result
|
281
|
+
|
282
|
+
# Test basic detail stats
|
283
|
+
stats = self.metadata_manager.get_table_detail_stats("test_db", "test_table")
|
284
|
+
|
285
|
+
self.assertIn("test_table", stats)
|
286
|
+
table_details = stats["test_table"]
|
287
|
+
self.assertEqual(len(table_details), 2)
|
288
|
+
|
289
|
+
# Check first object
|
290
|
+
obj1 = table_details[0]
|
291
|
+
self.assertEqual(obj1["object_name"], "obj1")
|
292
|
+
self.assertEqual(obj1["create_ts"], "2023-01-01 10:00:00")
|
293
|
+
self.assertEqual(obj1["delete_ts"], None)
|
294
|
+
self.assertEqual(obj1["row_cnt"], 100)
|
295
|
+
self.assertEqual(obj1["null_cnt"], 5)
|
296
|
+
self.assertEqual(obj1["original_size"], "1 MB")
|
297
|
+
self.assertEqual(obj1["compress_size"], "512 KB")
|
298
|
+
|
299
|
+
# Check second object
|
300
|
+
obj2 = table_details[1]
|
301
|
+
self.assertEqual(obj2["object_name"], "obj2")
|
302
|
+
self.assertEqual(obj2["delete_ts"], "2023-01-03 12:00:00")
|
303
|
+
self.assertEqual(obj2["original_size"], "2 MB")
|
304
|
+
self.assertEqual(obj2["compress_size"], "1 MB")
|
305
|
+
|
306
|
+
|
307
|
+
class TestTransactionMetadataManager(unittest.TestCase):
|
308
|
+
"""Test TransactionMetadataManager class"""
|
309
|
+
|
310
|
+
def setUp(self):
|
311
|
+
"""Set up test fixtures"""
|
312
|
+
self.mock_client = Mock()
|
313
|
+
self.mock_transaction = Mock()
|
314
|
+
self.metadata_manager = TransactionMetadataManager(self.mock_client, self.mock_transaction)
|
315
|
+
|
316
|
+
def test_init(self):
|
317
|
+
"""Test TransactionMetadataManager initialization"""
|
318
|
+
self.assertEqual(self.metadata_manager.client, self.mock_client)
|
319
|
+
self.assertEqual(self.metadata_manager.transaction_wrapper, self.mock_transaction)
|
320
|
+
|
321
|
+
def test_scan_uses_transaction(self):
|
322
|
+
"""Test that scan uses transaction wrapper"""
|
323
|
+
mock_result = Mock()
|
324
|
+
mock_result.fetchall.return_value = []
|
325
|
+
self.mock_transaction.execute.return_value = mock_result
|
326
|
+
|
327
|
+
result = self.metadata_manager.scan("test_db", "test_table")
|
328
|
+
|
329
|
+
# Verify transaction.execute was called
|
330
|
+
self.mock_transaction.execute.assert_called_once()
|
331
|
+
|
332
|
+
def test_get_table_brief_stats_uses_transaction(self):
|
333
|
+
"""Test that get_table_brief_stats uses transaction wrapper"""
|
334
|
+
mock_result = Mock()
|
335
|
+
mock_result.fetchall.return_value = [
|
336
|
+
Mock(
|
337
|
+
_mapping={
|
338
|
+
'object_name': 'obj1',
|
339
|
+
'rows_cnt': 100,
|
340
|
+
'null_cnt': 5,
|
341
|
+
'origin_size': 1048576,
|
342
|
+
'compress_size': 524288,
|
343
|
+
}
|
344
|
+
)
|
345
|
+
]
|
346
|
+
self.mock_transaction.execute.return_value = mock_result
|
347
|
+
|
348
|
+
stats = self.metadata_manager.get_table_brief_stats("test_db", "test_table")
|
349
|
+
|
350
|
+
# Verify transaction.execute was called
|
351
|
+
self.mock_transaction.execute.assert_called_once()
|
352
|
+
self.assertIn("test_table", stats)
|
353
|
+
|
354
|
+
def test_get_table_detail_stats_uses_transaction(self):
|
355
|
+
"""Test that get_table_detail_stats uses transaction wrapper"""
|
356
|
+
mock_result = Mock()
|
357
|
+
mock_result.fetchall.return_value = [
|
358
|
+
Mock(
|
359
|
+
_mapping={
|
360
|
+
'object_name': 'obj1',
|
361
|
+
'create_ts': '2023-01-01 10:00:00',
|
362
|
+
'delete_ts': None,
|
363
|
+
'rows_cnt': 100,
|
364
|
+
'null_cnt': 5,
|
365
|
+
'origin_size': 1048576,
|
366
|
+
'compress_size': 524288,
|
367
|
+
}
|
368
|
+
)
|
369
|
+
]
|
370
|
+
self.mock_transaction.execute.return_value = mock_result
|
371
|
+
|
372
|
+
stats = self.metadata_manager.get_table_detail_stats("test_db", "test_table")
|
373
|
+
|
374
|
+
# Verify transaction.execute was called
|
375
|
+
self.mock_transaction.execute.assert_called_once()
|
376
|
+
self.assertIn("test_table", stats)
|
377
|
+
|
378
|
+
|
379
|
+
class TestAsyncMetadataManager(unittest.TestCase):
|
380
|
+
"""Test AsyncMetadataManager class"""
|
381
|
+
|
382
|
+
def setUp(self):
|
383
|
+
"""Set up test fixtures"""
|
384
|
+
self.mock_client = AsyncMock()
|
385
|
+
self.metadata_manager = AsyncMetadataManager(self.mock_client)
|
386
|
+
|
387
|
+
def test_init(self):
|
388
|
+
"""Test AsyncMetadataManager initialization"""
|
389
|
+
self.assertEqual(self.metadata_manager.client, self.mock_client)
|
390
|
+
|
391
|
+
def test_scan_basic(self):
|
392
|
+
"""Test basic async scan functionality"""
|
393
|
+
|
394
|
+
async def _test():
|
395
|
+
# Mock the execute method
|
396
|
+
mock_result = Mock()
|
397
|
+
mock_result.fetchall.return_value = [
|
398
|
+
Mock(_mapping={'col_name': 'id', 'rows_cnt': 100, 'null_cnt': 0, 'origin_size': 1000})
|
399
|
+
]
|
400
|
+
self.mock_client.execute.return_value = mock_result
|
401
|
+
|
402
|
+
# Test basic scan
|
403
|
+
result = await self.metadata_manager.scan("test_db", "test_table")
|
404
|
+
|
405
|
+
# Verify SQL was called correctly
|
406
|
+
self.mock_client.execute.assert_called_once()
|
407
|
+
call_args = self.mock_client.execute.call_args[0][0]
|
408
|
+
self.assertIn("metadata_scan('test_db.test_table', '*')", str(call_args))
|
409
|
+
|
410
|
+
asyncio.run(_test())
|
411
|
+
|
412
|
+
def test_get_table_brief_stats(self):
|
413
|
+
"""Test async get_table_brief_stats method"""
|
414
|
+
|
415
|
+
async def _test():
|
416
|
+
# Mock the execute method
|
417
|
+
mock_result = Mock()
|
418
|
+
mock_result.fetchall.return_value = [
|
419
|
+
Mock(
|
420
|
+
_mapping={
|
421
|
+
'object_name': 'obj1',
|
422
|
+
'rows_cnt': 100,
|
423
|
+
'null_cnt': 5,
|
424
|
+
'origin_size': 1048576,
|
425
|
+
'compress_size': 524288,
|
426
|
+
}
|
427
|
+
)
|
428
|
+
]
|
429
|
+
self.mock_client.execute.return_value = mock_result
|
430
|
+
|
431
|
+
stats = await self.metadata_manager.get_table_brief_stats("test_db", "test_table")
|
432
|
+
|
433
|
+
self.assertIn("test_table", stats)
|
434
|
+
table_stats = stats["test_table"]
|
435
|
+
self.assertEqual(table_stats["total_objects"], 1)
|
436
|
+
self.assertEqual(table_stats["original_size"], "1 MB")
|
437
|
+
self.assertEqual(table_stats["compress_size"], "512 KB")
|
438
|
+
|
439
|
+
asyncio.run(_test())
|
440
|
+
|
441
|
+
def test_get_table_detail_stats(self):
|
442
|
+
"""Test async get_table_detail_stats method"""
|
443
|
+
|
444
|
+
async def _test():
|
445
|
+
# Mock the execute method
|
446
|
+
mock_result = Mock()
|
447
|
+
mock_result.fetchall.return_value = [
|
448
|
+
Mock(
|
449
|
+
_mapping={
|
450
|
+
'object_name': 'obj1',
|
451
|
+
'create_ts': '2023-01-01 10:00:00',
|
452
|
+
'delete_ts': None,
|
453
|
+
'rows_cnt': 100,
|
454
|
+
'null_cnt': 5,
|
455
|
+
'origin_size': 1048576,
|
456
|
+
'compress_size': 524288,
|
457
|
+
}
|
458
|
+
)
|
459
|
+
]
|
460
|
+
self.mock_client.execute.return_value = mock_result
|
461
|
+
|
462
|
+
stats = await self.metadata_manager.get_table_detail_stats("test_db", "test_table")
|
463
|
+
|
464
|
+
self.assertIn("test_table", stats)
|
465
|
+
table_details = stats["test_table"]
|
466
|
+
self.assertEqual(len(table_details), 1)
|
467
|
+
self.assertEqual(table_details[0]["object_name"], "obj1")
|
468
|
+
|
469
|
+
asyncio.run(_test())
|
470
|
+
|
471
|
+
|
472
|
+
class TestAsyncTransactionMetadataManager(unittest.TestCase):
|
473
|
+
"""Test AsyncTransactionMetadataManager class"""
|
474
|
+
|
475
|
+
def setUp(self):
|
476
|
+
"""Set up test fixtures"""
|
477
|
+
self.mock_client = AsyncMock()
|
478
|
+
self.mock_transaction = AsyncMock()
|
479
|
+
self.metadata_manager = AsyncTransactionMetadataManager(self.mock_client, self.mock_transaction)
|
480
|
+
|
481
|
+
def test_init(self):
|
482
|
+
"""Test AsyncTransactionMetadataManager initialization"""
|
483
|
+
self.assertEqual(self.metadata_manager.client, self.mock_client)
|
484
|
+
self.assertEqual(self.metadata_manager.transaction_wrapper, self.mock_transaction)
|
485
|
+
|
486
|
+
def test_scan_uses_transaction(self):
|
487
|
+
"""Test that async scan uses transaction wrapper"""
|
488
|
+
|
489
|
+
async def _test():
|
490
|
+
mock_result = Mock()
|
491
|
+
mock_result.fetchall.return_value = []
|
492
|
+
self.mock_transaction.execute.return_value = mock_result
|
493
|
+
|
494
|
+
result = await self.metadata_manager.scan("test_db", "test_table")
|
495
|
+
|
496
|
+
# Verify transaction.execute was called
|
497
|
+
self.mock_transaction.execute.assert_called_once()
|
498
|
+
|
499
|
+
asyncio.run(_test())
|
500
|
+
|
501
|
+
def test_get_table_brief_stats_uses_transaction(self):
|
502
|
+
"""Test that async get_table_brief_stats uses transaction wrapper"""
|
503
|
+
|
504
|
+
async def _test():
|
505
|
+
mock_result = Mock()
|
506
|
+
mock_result.fetchall.return_value = [
|
507
|
+
Mock(
|
508
|
+
_mapping={
|
509
|
+
'object_name': 'obj1',
|
510
|
+
'rows_cnt': 100,
|
511
|
+
'null_cnt': 5,
|
512
|
+
'origin_size': 1048576,
|
513
|
+
'compress_size': 524288,
|
514
|
+
}
|
515
|
+
)
|
516
|
+
]
|
517
|
+
self.mock_transaction.execute.return_value = mock_result
|
518
|
+
|
519
|
+
stats = await self.metadata_manager.get_table_brief_stats("test_db", "test_table")
|
520
|
+
|
521
|
+
# Verify transaction.execute was called
|
522
|
+
self.mock_transaction.execute.assert_called_once()
|
523
|
+
self.assertIn("test_table", stats)
|
524
|
+
|
525
|
+
asyncio.run(_test())
|
526
|
+
|
527
|
+
def test_get_table_detail_stats_uses_transaction(self):
|
528
|
+
"""Test that async get_table_detail_stats uses transaction wrapper"""
|
529
|
+
|
530
|
+
async def _test():
|
531
|
+
mock_result = Mock()
|
532
|
+
mock_result.fetchall.return_value = [
|
533
|
+
Mock(
|
534
|
+
_mapping={
|
535
|
+
'object_name': 'obj1',
|
536
|
+
'create_ts': '2023-01-01 10:00:00',
|
537
|
+
'delete_ts': None,
|
538
|
+
'rows_cnt': 100,
|
539
|
+
'null_cnt': 5,
|
540
|
+
'origin_size': 1048576,
|
541
|
+
'compress_size': 524288,
|
542
|
+
}
|
543
|
+
)
|
544
|
+
]
|
545
|
+
self.mock_transaction.execute.return_value = mock_result
|
546
|
+
|
547
|
+
stats = await self.metadata_manager.get_table_detail_stats("test_db", "test_table")
|
548
|
+
|
549
|
+
# Verify transaction.execute was called
|
550
|
+
self.mock_transaction.execute.assert_called_once()
|
551
|
+
self.assertIn("test_table", stats)
|
552
|
+
|
553
|
+
asyncio.run(_test())
|
554
|
+
|
555
|
+
|
556
|
+
if __name__ == '__main__':
|
557
|
+
unittest.main()
|