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.
Files changed (122) hide show
  1. matrixone/__init__.py +155 -0
  2. matrixone/account.py +723 -0
  3. matrixone/async_client.py +3913 -0
  4. matrixone/async_metadata_manager.py +311 -0
  5. matrixone/async_orm.py +123 -0
  6. matrixone/async_vector_index_manager.py +633 -0
  7. matrixone/base_client.py +208 -0
  8. matrixone/client.py +4672 -0
  9. matrixone/config.py +452 -0
  10. matrixone/connection_hooks.py +286 -0
  11. matrixone/exceptions.py +89 -0
  12. matrixone/logger.py +782 -0
  13. matrixone/metadata.py +820 -0
  14. matrixone/moctl.py +219 -0
  15. matrixone/orm.py +2277 -0
  16. matrixone/pitr.py +646 -0
  17. matrixone/pubsub.py +771 -0
  18. matrixone/restore.py +411 -0
  19. matrixone/search_vector_index.py +1176 -0
  20. matrixone/snapshot.py +550 -0
  21. matrixone/sql_builder.py +844 -0
  22. matrixone/sqlalchemy_ext/__init__.py +161 -0
  23. matrixone/sqlalchemy_ext/adapters.py +163 -0
  24. matrixone/sqlalchemy_ext/dialect.py +534 -0
  25. matrixone/sqlalchemy_ext/fulltext_index.py +895 -0
  26. matrixone/sqlalchemy_ext/fulltext_search.py +1686 -0
  27. matrixone/sqlalchemy_ext/hnsw_config.py +194 -0
  28. matrixone/sqlalchemy_ext/ivf_config.py +252 -0
  29. matrixone/sqlalchemy_ext/table_builder.py +351 -0
  30. matrixone/sqlalchemy_ext/vector_index.py +1721 -0
  31. matrixone/sqlalchemy_ext/vector_type.py +948 -0
  32. matrixone/version.py +580 -0
  33. matrixone_python_sdk-0.1.0.dist-info/METADATA +706 -0
  34. matrixone_python_sdk-0.1.0.dist-info/RECORD +122 -0
  35. matrixone_python_sdk-0.1.0.dist-info/WHEEL +5 -0
  36. matrixone_python_sdk-0.1.0.dist-info/entry_points.txt +5 -0
  37. matrixone_python_sdk-0.1.0.dist-info/licenses/LICENSE +200 -0
  38. matrixone_python_sdk-0.1.0.dist-info/top_level.txt +2 -0
  39. tests/__init__.py +19 -0
  40. tests/offline/__init__.py +20 -0
  41. tests/offline/conftest.py +77 -0
  42. tests/offline/test_account.py +703 -0
  43. tests/offline/test_async_client_query_comprehensive.py +1218 -0
  44. tests/offline/test_basic.py +54 -0
  45. tests/offline/test_case_sensitivity.py +227 -0
  46. tests/offline/test_connection_hooks_offline.py +287 -0
  47. tests/offline/test_dialect_schema_handling.py +609 -0
  48. tests/offline/test_explain_methods.py +346 -0
  49. tests/offline/test_filter_logical_in.py +237 -0
  50. tests/offline/test_fulltext_search_comprehensive.py +795 -0
  51. tests/offline/test_ivf_config.py +249 -0
  52. tests/offline/test_join_methods.py +281 -0
  53. tests/offline/test_join_sqlalchemy_compatibility.py +276 -0
  54. tests/offline/test_logical_in_method.py +237 -0
  55. tests/offline/test_matrixone_version_parsing.py +264 -0
  56. tests/offline/test_metadata_offline.py +557 -0
  57. tests/offline/test_moctl.py +300 -0
  58. tests/offline/test_moctl_simple.py +251 -0
  59. tests/offline/test_model_support_offline.py +359 -0
  60. tests/offline/test_model_support_simple.py +225 -0
  61. tests/offline/test_pinecone_filter_offline.py +377 -0
  62. tests/offline/test_pitr.py +585 -0
  63. tests/offline/test_pubsub.py +712 -0
  64. tests/offline/test_query_update.py +283 -0
  65. tests/offline/test_restore.py +445 -0
  66. tests/offline/test_snapshot_comprehensive.py +384 -0
  67. tests/offline/test_sql_escaping_edge_cases.py +551 -0
  68. tests/offline/test_sqlalchemy_integration.py +382 -0
  69. tests/offline/test_sqlalchemy_vector_integration.py +434 -0
  70. tests/offline/test_table_builder.py +198 -0
  71. tests/offline/test_unified_filter.py +398 -0
  72. tests/offline/test_unified_transaction.py +495 -0
  73. tests/offline/test_vector_index.py +238 -0
  74. tests/offline/test_vector_operations.py +688 -0
  75. tests/offline/test_vector_type.py +174 -0
  76. tests/offline/test_version_core.py +328 -0
  77. tests/offline/test_version_management.py +372 -0
  78. tests/offline/test_version_standalone.py +652 -0
  79. tests/online/__init__.py +20 -0
  80. tests/online/conftest.py +216 -0
  81. tests/online/test_account_management.py +194 -0
  82. tests/online/test_advanced_features.py +344 -0
  83. tests/online/test_async_client_interfaces.py +330 -0
  84. tests/online/test_async_client_online.py +285 -0
  85. tests/online/test_async_model_insert_online.py +293 -0
  86. tests/online/test_async_orm_online.py +300 -0
  87. tests/online/test_async_simple_query_online.py +802 -0
  88. tests/online/test_async_transaction_simple_query.py +300 -0
  89. tests/online/test_basic_connection.py +130 -0
  90. tests/online/test_client_online.py +238 -0
  91. tests/online/test_config.py +90 -0
  92. tests/online/test_config_validation.py +123 -0
  93. tests/online/test_connection_hooks_new_online.py +217 -0
  94. tests/online/test_dialect_schema_handling_online.py +331 -0
  95. tests/online/test_filter_logical_in_online.py +374 -0
  96. tests/online/test_fulltext_comprehensive.py +1773 -0
  97. tests/online/test_fulltext_label_online.py +433 -0
  98. tests/online/test_fulltext_search_online.py +842 -0
  99. tests/online/test_ivf_stats_online.py +506 -0
  100. tests/online/test_logger_integration.py +311 -0
  101. tests/online/test_matrixone_query_orm.py +540 -0
  102. tests/online/test_metadata_online.py +579 -0
  103. tests/online/test_model_insert_online.py +255 -0
  104. tests/online/test_mysql_driver_validation.py +213 -0
  105. tests/online/test_orm_advanced_features.py +2022 -0
  106. tests/online/test_orm_cte_integration.py +269 -0
  107. tests/online/test_orm_online.py +270 -0
  108. tests/online/test_pinecone_filter.py +708 -0
  109. tests/online/test_pubsub_operations.py +352 -0
  110. tests/online/test_query_methods.py +225 -0
  111. tests/online/test_query_update_online.py +433 -0
  112. tests/online/test_search_vector_index.py +557 -0
  113. tests/online/test_simple_fulltext_online.py +915 -0
  114. tests/online/test_snapshot_comprehensive.py +998 -0
  115. tests/online/test_sqlalchemy_engine_integration.py +336 -0
  116. tests/online/test_sqlalchemy_integration.py +425 -0
  117. tests/online/test_transaction_contexts.py +1219 -0
  118. tests/online/test_transaction_insert_methods.py +356 -0
  119. tests/online/test_transaction_query_methods.py +288 -0
  120. tests/online/test_unified_filter_online.py +529 -0
  121. tests/online/test_vector_comprehensive.py +706 -0
  122. 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()