matrixone-python-sdk 0.1.2__tar.gz → 0.1.4__tar.gz

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 (74) hide show
  1. {matrixone_python_sdk-0.1.2/matrixone_python_sdk.egg-info → matrixone_python_sdk-0.1.4}/PKG-INFO +1 -1
  2. {matrixone_python_sdk-0.1.2 → matrixone_python_sdk-0.1.4}/README.md +25 -0
  3. {matrixone_python_sdk-0.1.2 → matrixone_python_sdk-0.1.4}/matrixone/async_client.py +100 -3
  4. {matrixone_python_sdk-0.1.2 → matrixone_python_sdk-0.1.4}/matrixone/async_metadata_manager.py +3 -1
  5. {matrixone_python_sdk-0.1.2 → matrixone_python_sdk-0.1.4}/matrixone/client.py +102 -2
  6. {matrixone_python_sdk-0.1.2 → matrixone_python_sdk-0.1.4}/matrixone/connection_hooks.py +8 -7
  7. {matrixone_python_sdk-0.1.2 → matrixone_python_sdk-0.1.4}/matrixone/exceptions.py +0 -24
  8. matrixone_python_sdk-0.1.4/matrixone/index_utils.py +143 -0
  9. {matrixone_python_sdk-0.1.2 → matrixone_python_sdk-0.1.4}/matrixone/metadata.py +3 -2
  10. {matrixone_python_sdk-0.1.2 → matrixone_python_sdk-0.1.4}/matrixone/moctl.py +0 -2
  11. {matrixone_python_sdk-0.1.2 → matrixone_python_sdk-0.1.4}/matrixone/sqlalchemy_ext/__init__.py +1 -0
  12. {matrixone_python_sdk-0.1.2 → matrixone_python_sdk-0.1.4}/matrixone/version.py +0 -2
  13. {matrixone_python_sdk-0.1.2 → matrixone_python_sdk-0.1.4/matrixone_python_sdk.egg-info}/PKG-INFO +1 -1
  14. {matrixone_python_sdk-0.1.2 → matrixone_python_sdk-0.1.4}/matrixone_python_sdk.egg-info/SOURCES.txt +1 -0
  15. {matrixone_python_sdk-0.1.2 → matrixone_python_sdk-0.1.4}/pyproject.toml +1 -1
  16. {matrixone_python_sdk-0.1.2 → matrixone_python_sdk-0.1.4}/LICENSE +0 -0
  17. {matrixone_python_sdk-0.1.2 → matrixone_python_sdk-0.1.4}/MANIFEST.in +0 -0
  18. {matrixone_python_sdk-0.1.2 → matrixone_python_sdk-0.1.4}/README_USER.md +0 -0
  19. {matrixone_python_sdk-0.1.2 → matrixone_python_sdk-0.1.4}/examples/example_01_basic_connection.py +0 -0
  20. {matrixone_python_sdk-0.1.2 → matrixone_python_sdk-0.1.4}/examples/example_02_account_management.py +0 -0
  21. {matrixone_python_sdk-0.1.2 → matrixone_python_sdk-0.1.4}/examples/example_03_async_operations.py +0 -0
  22. {matrixone_python_sdk-0.1.2 → matrixone_python_sdk-0.1.4}/examples/example_04_transaction_management.py +0 -0
  23. {matrixone_python_sdk-0.1.2 → matrixone_python_sdk-0.1.4}/examples/example_05_snapshot_restore.py +0 -0
  24. {matrixone_python_sdk-0.1.2 → matrixone_python_sdk-0.1.4}/examples/example_06_sqlalchemy_integration.py +0 -0
  25. {matrixone_python_sdk-0.1.2 → matrixone_python_sdk-0.1.4}/examples/example_07_advanced_features.py +0 -0
  26. {matrixone_python_sdk-0.1.2 → matrixone_python_sdk-0.1.4}/examples/example_08_pubsub_operations.py +0 -0
  27. {matrixone_python_sdk-0.1.2 → matrixone_python_sdk-0.1.4}/examples/example_09_logger_integration.py +0 -0
  28. {matrixone_python_sdk-0.1.2 → matrixone_python_sdk-0.1.4}/examples/example_10_version_management.py +0 -0
  29. {matrixone_python_sdk-0.1.2 → matrixone_python_sdk-0.1.4}/examples/example_11_matrixone_version_demo.py +0 -0
  30. {matrixone_python_sdk-0.1.2 → matrixone_python_sdk-0.1.4}/examples/example_12_vector_basics.py +0 -0
  31. {matrixone_python_sdk-0.1.2 → matrixone_python_sdk-0.1.4}/examples/example_13_vector_indexes.py +0 -0
  32. {matrixone_python_sdk-0.1.2 → matrixone_python_sdk-0.1.4}/examples/example_14_vector_search.py +0 -0
  33. {matrixone_python_sdk-0.1.2 → matrixone_python_sdk-0.1.4}/examples/example_15_vector_advanced.py +0 -0
  34. {matrixone_python_sdk-0.1.2 → matrixone_python_sdk-0.1.4}/examples/example_18_snapshot_orm.py +0 -0
  35. {matrixone_python_sdk-0.1.2 → matrixone_python_sdk-0.1.4}/examples/example_19_sqlalchemy_style_orm.py +0 -0
  36. {matrixone_python_sdk-0.1.2 → matrixone_python_sdk-0.1.4}/examples/example_20_sqlalchemy_engine_integration.py +0 -0
  37. {matrixone_python_sdk-0.1.2 → matrixone_python_sdk-0.1.4}/examples/example_21_advanced_orm_features.py +0 -0
  38. {matrixone_python_sdk-0.1.2 → matrixone_python_sdk-0.1.4}/examples/example_22_unified_sql_builder.py +0 -0
  39. {matrixone_python_sdk-0.1.2 → matrixone_python_sdk-0.1.4}/examples/example_24_query_update.py +0 -0
  40. {matrixone_python_sdk-0.1.2 → matrixone_python_sdk-0.1.4}/examples/example_25_metadata_operations.py +0 -0
  41. {matrixone_python_sdk-0.1.2 → matrixone_python_sdk-0.1.4}/examples/example_connection_hooks.py +0 -0
  42. {matrixone_python_sdk-0.1.2 → matrixone_python_sdk-0.1.4}/examples/example_dynamic_logging.py +0 -0
  43. {matrixone_python_sdk-0.1.2 → matrixone_python_sdk-0.1.4}/examples/example_ivf_stats_complete.py +0 -0
  44. {matrixone_python_sdk-0.1.2 → matrixone_python_sdk-0.1.4}/matrixone/__init__.py +0 -0
  45. {matrixone_python_sdk-0.1.2 → matrixone_python_sdk-0.1.4}/matrixone/account.py +0 -0
  46. {matrixone_python_sdk-0.1.2 → matrixone_python_sdk-0.1.4}/matrixone/async_orm.py +0 -0
  47. {matrixone_python_sdk-0.1.2 → matrixone_python_sdk-0.1.4}/matrixone/async_vector_index_manager.py +0 -0
  48. {matrixone_python_sdk-0.1.2 → matrixone_python_sdk-0.1.4}/matrixone/base_client.py +0 -0
  49. {matrixone_python_sdk-0.1.2 → matrixone_python_sdk-0.1.4}/matrixone/config.py +0 -0
  50. {matrixone_python_sdk-0.1.2 → matrixone_python_sdk-0.1.4}/matrixone/logger.py +0 -0
  51. {matrixone_python_sdk-0.1.2 → matrixone_python_sdk-0.1.4}/matrixone/orm.py +0 -0
  52. {matrixone_python_sdk-0.1.2 → matrixone_python_sdk-0.1.4}/matrixone/pitr.py +0 -0
  53. {matrixone_python_sdk-0.1.2 → matrixone_python_sdk-0.1.4}/matrixone/pubsub.py +0 -0
  54. {matrixone_python_sdk-0.1.2 → matrixone_python_sdk-0.1.4}/matrixone/restore.py +0 -0
  55. {matrixone_python_sdk-0.1.2 → matrixone_python_sdk-0.1.4}/matrixone/search_vector_index.py +0 -0
  56. {matrixone_python_sdk-0.1.2 → matrixone_python_sdk-0.1.4}/matrixone/snapshot.py +0 -0
  57. {matrixone_python_sdk-0.1.2 → matrixone_python_sdk-0.1.4}/matrixone/sql_builder.py +0 -0
  58. {matrixone_python_sdk-0.1.2 → matrixone_python_sdk-0.1.4}/matrixone/sqlalchemy_ext/adapters.py +0 -0
  59. {matrixone_python_sdk-0.1.2 → matrixone_python_sdk-0.1.4}/matrixone/sqlalchemy_ext/dialect.py +0 -0
  60. {matrixone_python_sdk-0.1.2 → matrixone_python_sdk-0.1.4}/matrixone/sqlalchemy_ext/fulltext_index.py +0 -0
  61. {matrixone_python_sdk-0.1.2 → matrixone_python_sdk-0.1.4}/matrixone/sqlalchemy_ext/fulltext_search.py +0 -0
  62. {matrixone_python_sdk-0.1.2 → matrixone_python_sdk-0.1.4}/matrixone/sqlalchemy_ext/hnsw_config.py +0 -0
  63. {matrixone_python_sdk-0.1.2 → matrixone_python_sdk-0.1.4}/matrixone/sqlalchemy_ext/ivf_config.py +0 -0
  64. {matrixone_python_sdk-0.1.2 → matrixone_python_sdk-0.1.4}/matrixone/sqlalchemy_ext/table_builder.py +0 -0
  65. {matrixone_python_sdk-0.1.2 → matrixone_python_sdk-0.1.4}/matrixone/sqlalchemy_ext/vector_index.py +0 -0
  66. {matrixone_python_sdk-0.1.2 → matrixone_python_sdk-0.1.4}/matrixone/sqlalchemy_ext/vector_type.py +0 -0
  67. {matrixone_python_sdk-0.1.2 → matrixone_python_sdk-0.1.4}/matrixone_python_sdk.egg-info/dependency_links.txt +0 -0
  68. {matrixone_python_sdk-0.1.2 → matrixone_python_sdk-0.1.4}/matrixone_python_sdk.egg-info/entry_points.txt +0 -0
  69. {matrixone_python_sdk-0.1.2 → matrixone_python_sdk-0.1.4}/matrixone_python_sdk.egg-info/not-zip-safe +0 -0
  70. {matrixone_python_sdk-0.1.2 → matrixone_python_sdk-0.1.4}/matrixone_python_sdk.egg-info/requires.txt +0 -0
  71. {matrixone_python_sdk-0.1.2 → matrixone_python_sdk-0.1.4}/matrixone_python_sdk.egg-info/top_level.txt +0 -0
  72. {matrixone_python_sdk-0.1.2 → matrixone_python_sdk-0.1.4}/requirements.txt +0 -0
  73. {matrixone_python_sdk-0.1.2 → matrixone_python_sdk-0.1.4}/setup.cfg +0 -0
  74. {matrixone_python_sdk-0.1.2 → matrixone_python_sdk-0.1.4}/setup.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: matrixone-python-sdk
3
- Version: 0.1.2
3
+ Version: 0.1.4
4
4
  Summary: A comprehensive Python SDK for MatrixOne database operations with vector search, fulltext search, and advanced features
5
5
  Home-page: https://github.com/matrixorigin/matrixone
6
6
  Author: MatrixOne Team
@@ -31,6 +31,10 @@ A comprehensive Python SDK for MatrixOne that provides SQLAlchemy-like interface
31
31
  - Table size and row count statistics
32
32
  - Column data distribution analysis
33
33
  - Index usage metrics
34
+ - 🔍 **Secondary Index Verification**: Verify consistency of secondary indexes with main table
35
+ - Get all secondary index table names
36
+ - Get specific index table by index name
37
+ - Verify row counts across main table and all indexes in single query
34
38
  - 📸 **Snapshot Management**: Create and manage database snapshots at multiple levels
35
39
  - ⏰ **Point-in-Time Recovery**: PITR functionality for precise data recovery
36
40
  - 🔄 **Table Cloning**: Clone databases and tables efficiently with data replication
@@ -323,6 +327,27 @@ print(f"Original size: {table_stats['original_size']} bytes")
323
327
  print(f"Compressed size: {table_stats['compress_size']} bytes")
324
328
  ```
325
329
 
330
+ ### Secondary Index Verification
331
+
332
+ ```python
333
+ # Get all secondary index tables
334
+ index_tables = client.get_secondary_index_tables('my_table')
335
+ print(f"Found {len(index_tables)} secondary indexes")
336
+
337
+ # Get specific index by name
338
+ physical_table = client.get_secondary_index_table_by_name('my_table', 'idx_name')
339
+ print(f"Index 'idx_name' -> {physical_table}")
340
+
341
+ # Verify all indexes have same count as main table
342
+ try:
343
+ count = client.verify_table_index_counts('my_table')
344
+ print(f"✓ Verification passed! Row count: {count}")
345
+ except ValueError as e:
346
+ print(f"✗ Index mismatch detected:")
347
+ print(e)
348
+ # Error includes all count details for debugging
349
+ ```
350
+
326
351
  ### Version Management
327
352
 
328
353
  ```python
@@ -34,9 +34,10 @@ from datetime import datetime
34
34
  from typing import Any, Callable, Dict, List, Optional, Tuple, Union
35
35
 
36
36
  from .account import Account, User
37
+ from .async_metadata_manager import AsyncMetadataManager
37
38
  from .async_vector_index_manager import AsyncVectorManager
38
39
  from .base_client import BaseMatrixOneClient, BaseMatrixOneExecutor
39
- from .connection_hooks import ConnectionHook, ConnectionAction, create_connection_hook
40
+ from .connection_hooks import ConnectionAction, ConnectionHook, create_connection_hook
40
41
  from .exceptions import (
41
42
  AccountError,
42
43
  ConnectionError,
@@ -48,7 +49,6 @@ from .exceptions import (
48
49
  SnapshotError,
49
50
  )
50
51
  from .logger import MatrixOneLogger, create_default_logger
51
- from .async_metadata_manager import AsyncMetadataManager
52
52
  from .pitr import Pitr
53
53
  from .pubsub import Publication, Subscription
54
54
  from .snapshot import Snapshot, SnapshotLevel
@@ -1837,7 +1837,6 @@ class AsyncClient(BaseMatrixOneClient):
1837
1837
  """Cleanup when object is garbage collected"""
1838
1838
  # Don't try to cleanup in __del__ as it can cause issues with event loops
1839
1839
  # The fixture should handle proper cleanup
1840
- pass
1841
1840
 
1842
1841
  def get_sqlalchemy_engine(self) -> AsyncEngine:
1843
1842
  """
@@ -1920,6 +1919,7 @@ class AsyncClient(BaseMatrixOneClient):
1920
1919
  and other SDK components. External users should use execute() instead.
1921
1920
  """
1922
1921
  import time
1922
+
1923
1923
  from sqlalchemy import text
1924
1924
 
1925
1925
  start_time = time.time()
@@ -2554,6 +2554,103 @@ class AsyncClient(BaseMatrixOneClient):
2554
2554
  except Exception as e:
2555
2555
  raise QueryError(f"Failed to get git version: {e}")
2556
2556
 
2557
+ async def get_secondary_index_tables(self, table_name: str) -> List[str]:
2558
+ """
2559
+ Get all secondary index table names for a given table in the current database (async version).
2560
+
2561
+ Args:
2562
+ table_name: Name of the table to get secondary indexes for
2563
+
2564
+ Returns:
2565
+ List of secondary index table names
2566
+
2567
+ Examples::
2568
+
2569
+ >>> async with AsyncClient() as client:
2570
+ ... await client.connect(host='localhost', port=6001, user='root', password='111', database='test')
2571
+ ... index_tables = await client.get_secondary_index_tables('cms_all_content_chunk_info')
2572
+ ... print(index_tables)
2573
+ """
2574
+ from .index_utils import build_get_index_tables_sql
2575
+
2576
+ # Get current database from connection params
2577
+ database = self._connection_params.get('database') if hasattr(self, '_connection_params') else None
2578
+
2579
+ sql, params = build_get_index_tables_sql(table_name, database)
2580
+ result = await self.execute(sql, params)
2581
+ return [row[0] for row in result.fetchall()]
2582
+
2583
+ async def get_secondary_index_table_by_name(self, table_name: str, index_name: str) -> Optional[str]:
2584
+ """
2585
+ Get the physical table name of a secondary index by its index name in the current database (async version).
2586
+
2587
+ Args:
2588
+ table_name: Name of the table
2589
+ index_name: Name of the secondary index
2590
+
2591
+ Returns:
2592
+ Physical table name of the secondary index, or None if not found
2593
+
2594
+ Examples::
2595
+
2596
+ >>> async with AsyncClient() as client:
2597
+ ... await client.connect(host='localhost', port=6001, user='root', password='111', database='test')
2598
+ ... index_table = await client.get_secondary_index_table_by_name('cms_all_content_chunk_info', 'cms_id')
2599
+ ... print(index_table)
2600
+ """
2601
+ from .index_utils import build_get_index_table_by_name_sql
2602
+
2603
+ # Get current database from connection params
2604
+ database = self._connection_params.get('database') if hasattr(self, '_connection_params') else None
2605
+
2606
+ sql, params = build_get_index_table_by_name_sql(table_name, index_name, database)
2607
+ result = await self.execute(sql, params)
2608
+ row = result.fetchone()
2609
+ return row[0] if row else None
2610
+
2611
+ async def verify_table_index_counts(self, table_name: str) -> int:
2612
+ """
2613
+ Verify that the main table and all its secondary index tables have the same row count (async version).
2614
+
2615
+ This method compares the COUNT(*) of the main table with all its secondary index tables
2616
+ in a single SQL query for consistency. If counts don't match, raises an exception.
2617
+
2618
+ Args:
2619
+ table_name: Name of the table to verify
2620
+
2621
+ Returns:
2622
+ Row count (int) if verification succeeds
2623
+
2624
+ Raises:
2625
+ ValueError: If any secondary index table has a different count than the main table,
2626
+ with details about all counts in the error message
2627
+
2628
+ Examples::
2629
+
2630
+ >>> async with AsyncClient() as client:
2631
+ ... await client.connect(host='localhost', port=6001, user='root', password='111', database='test')
2632
+ ... count = await client.verify_table_index_counts('cms_all_content_chunk_info')
2633
+ ... print(f"✓ Verification passed, row count: {count}")
2634
+
2635
+ >>> # If verification fails:
2636
+ >>> try:
2637
+ ... count = await client.verify_table_index_counts('some_table')
2638
+ ... except ValueError as e:
2639
+ ... print(f"Verification failed: {e}")
2640
+ """
2641
+ from .index_utils import build_verify_counts_sql, process_verify_result
2642
+
2643
+ # Get all secondary index tables
2644
+ index_tables = await self.get_secondary_index_tables(table_name)
2645
+
2646
+ # Build and execute verification SQL
2647
+ sql = build_verify_counts_sql(table_name, index_tables)
2648
+ result = await self.execute(sql)
2649
+ row = result.fetchone()
2650
+
2651
+ # Process result and raise exception if verification fails
2652
+ return process_verify_result(table_name, index_tables, row)
2653
+
2557
2654
  async def __aenter__(self):
2558
2655
  return self
2559
2656
 
@@ -19,8 +19,10 @@ This module provides async metadata scanning capabilities for MatrixOne tables,
19
19
  allowing users to analyze table statistics, column information, and data distribution.
20
20
  """
21
21
 
22
- from typing import Optional, List, Dict, Any, Union
22
+ from typing import Any, Dict, List, Optional, Union
23
+
23
24
  from sqlalchemy.engine import Result
25
+
24
26
  from .metadata import BaseMetadataManager, MetadataColumn, MetadataRow
25
27
 
26
28
 
@@ -30,7 +30,7 @@ from sqlalchemy.engine import Engine
30
30
 
31
31
  from .account import AccountManager, TransactionAccountManager
32
32
  from .base_client import BaseMatrixOneClient, BaseMatrixOneExecutor
33
- from .connection_hooks import ConnectionHook, ConnectionAction, create_connection_hook
33
+ from .connection_hooks import ConnectionAction, ConnectionHook, create_connection_hook
34
34
  from .exceptions import ConnectionError, QueryError
35
35
  from .logger import MatrixOneLogger, create_default_logger
36
36
  from .metadata import MetadataManager, TransactionMetadataManager
@@ -1698,8 +1698,9 @@ class Client(BaseMatrixOneClient):
1698
1698
  table_name = model_class.__tablename__
1699
1699
  table = model_class.__table__
1700
1700
 
1701
+ from sqlalchemy.schema import CreateIndex, CreateTable
1702
+
1701
1703
  from .sqlalchemy_ext import FulltextIndex, VectorIndex
1702
- from sqlalchemy.schema import CreateTable, CreateIndex
1703
1704
 
1704
1705
  try:
1705
1706
  engine_context = self.get_sqlalchemy_engine().begin()
@@ -2612,6 +2613,105 @@ class Client(BaseMatrixOneClient):
2612
2613
 
2613
2614
  return self
2614
2615
 
2616
+ def get_secondary_index_tables(self, table_name: str) -> List[str]:
2617
+ """
2618
+ Get all secondary index table names for a given table in the current database.
2619
+
2620
+ Args:
2621
+ table_name: Name of the table to get secondary indexes for
2622
+
2623
+ Returns:
2624
+ List of secondary index table names
2625
+
2626
+ Examples::
2627
+
2628
+ >>> client = Client()
2629
+ >>> client.connect(host='localhost', port=6001, user='root', password='111', database='test')
2630
+ >>> index_tables = client.get_secondary_index_tables('cms_all_content_chunk_info')
2631
+ >>> print(index_tables)
2632
+ ['__mo_index_secondary_..._cms_id', '__mo_index_secondary_..._idx_all_content_length']
2633
+ """
2634
+ from .index_utils import build_get_index_tables_sql
2635
+
2636
+ # Get current database from connection params
2637
+ database = self._connection_params.get('database') if hasattr(self, '_connection_params') else None
2638
+
2639
+ sql, params = build_get_index_tables_sql(table_name, database)
2640
+ result = self.execute(sql, params)
2641
+ return [row[0] for row in result.fetchall()]
2642
+
2643
+ def get_secondary_index_table_by_name(self, table_name: str, index_name: str) -> Optional[str]:
2644
+ """
2645
+ Get the physical table name of a secondary index by its index name in the current database.
2646
+
2647
+ Args:
2648
+ table_name: Name of the table
2649
+ index_name: Name of the secondary index
2650
+
2651
+ Returns:
2652
+ Physical table name of the secondary index, or None if not found
2653
+
2654
+ Examples::
2655
+
2656
+ >>> client = Client()
2657
+ >>> client.connect(host='localhost', port=6001, user='root', password='111', database='test')
2658
+ >>> index_table = client.get_secondary_index_table_by_name('cms_all_content_chunk_info', 'cms_id')
2659
+ >>> print(index_table)
2660
+ '__mo_index_secondary_018cfbda-bde1-7c3e-805c-3f8e71769f75_cms_id'
2661
+ """
2662
+ from .index_utils import build_get_index_table_by_name_sql
2663
+
2664
+ # Get current database from connection params
2665
+ database = self._connection_params.get('database') if hasattr(self, '_connection_params') else None
2666
+
2667
+ sql, params = build_get_index_table_by_name_sql(table_name, index_name, database)
2668
+ result = self.execute(sql, params)
2669
+ row = result.fetchone()
2670
+ return row[0] if row else None
2671
+
2672
+ def verify_table_index_counts(self, table_name: str) -> int:
2673
+ """
2674
+ Verify that the main table and all its secondary index tables have the same row count.
2675
+
2676
+ This method compares the COUNT(*) of the main table with all its secondary index tables
2677
+ in a single SQL query for consistency. If counts don't match, raises an exception.
2678
+
2679
+ Args:
2680
+ table_name: Name of the table to verify
2681
+
2682
+ Returns:
2683
+ Row count (int) if verification succeeds
2684
+
2685
+ Raises:
2686
+ ValueError: If any secondary index table has a different count than the main table,
2687
+ with details about all counts in the error message
2688
+
2689
+ Examples::
2690
+
2691
+ >>> client = Client()
2692
+ >>> client.connect(host='localhost', port=6001, user='root', password='111', database='test')
2693
+ >>> count = client.verify_table_index_counts('cms_all_content_chunk_info')
2694
+ >>> print(f"✓ Verification passed, row count: {count}")
2695
+
2696
+ >>> # If verification fails:
2697
+ >>> try:
2698
+ ... count = client.verify_table_index_counts('some_table')
2699
+ ... except ValueError as e:
2700
+ ... print(f"Verification failed: {e}")
2701
+ """
2702
+ from .index_utils import build_verify_counts_sql, process_verify_result
2703
+
2704
+ # Get all secondary index tables
2705
+ index_tables = self.get_secondary_index_tables(table_name)
2706
+
2707
+ # Build and execute verification SQL
2708
+ sql = build_verify_counts_sql(table_name, index_tables)
2709
+ result = self.execute(sql)
2710
+ row = result.fetchone()
2711
+
2712
+ # Process result and raise exception if verification fails
2713
+ return process_verify_result(table_name, index_tables, row)
2714
+
2615
2715
  def __exit__(self, exc_type, exc_val, exc_tb):
2616
2716
  self.disconnect()
2617
2717
 
@@ -18,6 +18,7 @@ Connection hooks for MatrixOne clients
18
18
 
19
19
  from enum import Enum
20
20
  from typing import Callable, List, Optional, Union
21
+
21
22
  from sqlalchemy import event
22
23
  from sqlalchemy.engine import Engine
23
24
  from sqlalchemy.ext.asyncio import AsyncEngine
@@ -68,13 +69,13 @@ class ConnectionHook:
68
69
  event.listen(engine.sync_engine, "connect", self._on_connect_sync)
69
70
  event.listen(engine.sync_engine, "before_cursor_execute", self._on_before_cursor_execute)
70
71
  if hasattr(self._client_ref, 'logger'):
71
- self._client_ref.logger.info("Attached connection hook to async engine")
72
+ self._client_ref.logger.debug("Attached connection hook to async engine")
72
73
  else:
73
74
  # For sync engines, listen to both connect and before_cursor_execute events
74
75
  event.listen(engine, "connect", self._on_connect_sync)
75
76
  event.listen(engine, "before_cursor_execute", self._on_before_cursor_execute)
76
77
  if hasattr(self._client_ref, 'logger'):
77
- self._client_ref.logger.info("Attached connection hook to sync engine")
78
+ self._client_ref.logger.debug("Attached connection hook to sync engine")
78
79
 
79
80
  def _on_connect_sync(self, dbapi_connection, connection_record):
80
81
  """SQLAlchemy event handler for new connections (sync)"""
@@ -85,7 +86,7 @@ class ConnectionHook:
85
86
  try:
86
87
  # Log that the hook is being executed
87
88
  if hasattr(self._client_ref, 'logger'):
88
- self._client_ref.logger.info(f"Executing connection hook on new connection {conn_id}")
89
+ self._client_ref.logger.debug(f"Executing connection hook on new connection {conn_id}")
89
90
  # Pass the connection to avoid creating new connections
90
91
  self.execute_sync_with_connection(self._client_ref, dbapi_connection)
91
92
  self._executed_connections.add(conn_id)
@@ -103,7 +104,7 @@ class ConnectionHook:
103
104
  try:
104
105
  # Log that the hook is being executed
105
106
  if hasattr(self._client_ref, 'logger'):
106
- self._client_ref.logger.info(f"Executing connection hook on connection {conn_id}")
107
+ self._client_ref.logger.debug(f"Executing connection hook on connection {conn_id}")
107
108
  # Use the connection to avoid creating new connections
108
109
  self.execute_sync_with_connection(self._client_ref, conn.connection)
109
110
  self._executed_connections.add(conn_id)
@@ -211,7 +212,7 @@ class ConnectionHook:
211
212
  cursor = dbapi_connection.cursor()
212
213
  cursor.execute("SET experimental_ivf_index = 1")
213
214
  cursor.close()
214
- client.logger.info("✓ Enabled IVF vector operations")
215
+ client.logger.debug("✓ Enabled IVF vector operations")
215
216
  except Exception as e:
216
217
  client.logger.warning(f"Failed to enable IVF: {e}")
217
218
 
@@ -222,7 +223,7 @@ class ConnectionHook:
222
223
  cursor = dbapi_connection.cursor()
223
224
  cursor.execute("SET experimental_hnsw_index = 1")
224
225
  cursor.close()
225
- client.logger.info("✓ Enabled HNSW vector operations")
226
+ client.logger.debug("✓ Enabled HNSW vector operations")
226
227
  except Exception as e:
227
228
  client.logger.warning(f"Failed to enable HNSW: {e}")
228
229
 
@@ -233,7 +234,7 @@ class ConnectionHook:
233
234
  cursor = dbapi_connection.cursor()
234
235
  cursor.execute("SET experimental_fulltext_index = 1")
235
236
  cursor.close()
236
- client.logger.info("✓ Enabled fulltext search operations")
237
+ client.logger.debug("✓ Enabled fulltext search operations")
237
238
  except Exception as e:
238
239
  client.logger.warning(f"Failed to enable fulltext: {e}")
239
240
 
@@ -20,70 +20,46 @@ MatrixOne SDK Exceptions
20
20
  class MatrixOneError(Exception):
21
21
  """Base exception for all MatrixOne SDK errors"""
22
22
 
23
- pass
24
-
25
23
 
26
24
  class ConnectionError(MatrixOneError):
27
25
  """Raised when connection to MatrixOne fails"""
28
26
 
29
- pass
30
-
31
27
 
32
28
  class QueryError(MatrixOneError):
33
29
  """Raised when SQL query execution fails"""
34
30
 
35
- pass
36
-
37
31
 
38
32
  class ConfigurationError(MatrixOneError):
39
33
  """Raised when configuration is invalid"""
40
34
 
41
- pass
42
-
43
35
 
44
36
  class SnapshotError(MatrixOneError):
45
37
  """Raised when snapshot operations fail"""
46
38
 
47
- pass
48
-
49
39
 
50
40
  class CloneError(MatrixOneError):
51
41
  """Raised when clone operations fail"""
52
42
 
53
- pass
54
-
55
43
 
56
44
  class MoCtlError(MatrixOneError):
57
45
  """Raised when mo_ctl operations fail"""
58
46
 
59
- pass
60
-
61
47
 
62
48
  class RestoreError(MatrixOneError):
63
49
  """Raised when restore operations fail"""
64
50
 
65
- pass
66
-
67
51
 
68
52
  class PitrError(MatrixOneError):
69
53
  """Raised when PITR operations fail"""
70
54
 
71
- pass
72
-
73
55
 
74
56
  class PubSubError(MatrixOneError):
75
57
  """Raised when publish-subscribe operations fail"""
76
58
 
77
- pass
78
-
79
59
 
80
60
  class AccountError(MatrixOneError):
81
61
  """Raised when account management operations fail"""
82
62
 
83
- pass
84
-
85
63
 
86
64
  class VersionError(MatrixOneError):
87
65
  """Raised when version compatibility check fails"""
88
-
89
- pass
@@ -0,0 +1,143 @@
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
+ Index utilities - Shared logic for secondary index operations
17
+ """
18
+
19
+ from typing import List, Tuple
20
+
21
+
22
+ def build_get_index_tables_sql(table_name: str, database: str = None) -> Tuple[str, Tuple]:
23
+ """
24
+ Build SQL to get all secondary index table names for a given table.
25
+
26
+ Args:
27
+ table_name: Name of the table
28
+ database: Name of the database (optional, but recommended to avoid cross-database conflicts)
29
+
30
+ Returns:
31
+ Tuple of (sql, params)
32
+ """
33
+ if database:
34
+ sql = """
35
+ SELECT DISTINCT index_table_name
36
+ FROM mo_catalog.mo_indexes
37
+ JOIN mo_catalog.mo_tables ON mo_indexes.table_id = mo_tables.rel_id
38
+ WHERE relname = ? AND reldatabase = ? AND type = 'MULTIPLE'
39
+ """
40
+ return sql, (table_name, database)
41
+ else:
42
+ # Fallback to old behavior if database is not provided
43
+ sql = """
44
+ SELECT DISTINCT index_table_name
45
+ FROM mo_catalog.mo_indexes
46
+ JOIN mo_catalog.mo_tables ON mo_indexes.table_id = mo_tables.rel_id
47
+ WHERE relname = ? AND type = 'MULTIPLE'
48
+ """
49
+ return sql, (table_name,)
50
+
51
+
52
+ def build_get_index_table_by_name_sql(table_name: str, index_name: str, database: str = None) -> Tuple[str, Tuple]:
53
+ """
54
+ Build SQL to get the physical table name of a secondary index by its index name.
55
+
56
+ Args:
57
+ table_name: Name of the table
58
+ index_name: Name of the secondary index
59
+ database: Name of the database (optional, but recommended to avoid cross-database conflicts)
60
+
61
+ Returns:
62
+ Tuple of (sql, params)
63
+ """
64
+ if database:
65
+ sql = """
66
+ SELECT DISTINCT index_table_name
67
+ FROM mo_catalog.mo_indexes
68
+ JOIN mo_catalog.mo_tables ON mo_indexes.table_id = mo_tables.rel_id
69
+ WHERE relname = ? AND name = ? AND reldatabase = ?
70
+ """
71
+ return sql, (table_name, index_name, database)
72
+ else:
73
+ # Fallback to old behavior if database is not provided
74
+ sql = """
75
+ SELECT DISTINCT index_table_name
76
+ FROM mo_catalog.mo_indexes
77
+ JOIN mo_catalog.mo_tables ON mo_indexes.table_id = mo_tables.rel_id
78
+ WHERE relname = ? AND name = ?
79
+ """
80
+ return sql, (table_name, index_name)
81
+
82
+
83
+ def build_verify_counts_sql(table_name: str, index_tables: List[str]) -> str:
84
+ """
85
+ Build SQL to verify counts of main table and all index tables in a single query.
86
+
87
+ Args:
88
+ table_name: Name of the main table
89
+ index_tables: List of index table names
90
+
91
+ Returns:
92
+ SQL string
93
+ """
94
+ if not index_tables:
95
+ return f"SELECT COUNT(*) FROM `{table_name}`"
96
+
97
+ select_parts = [f"(SELECT COUNT(*) FROM `{table_name}`) as main_count"]
98
+ for idx, index_table in enumerate(index_tables):
99
+ select_parts.append(f"(SELECT COUNT(*) FROM `{index_table}`) as idx{idx}_count")
100
+
101
+ return "SELECT " + ", ".join(select_parts)
102
+
103
+
104
+ def process_verify_result(table_name: str, index_tables: List[str], row: Tuple) -> int:
105
+ """
106
+ Process the verification result and raise exception if counts don't match.
107
+
108
+ Args:
109
+ table_name: Name of the main table
110
+ index_tables: List of index table names
111
+ row: Result row from the verification SQL
112
+
113
+ Returns:
114
+ Row count if verification succeeds
115
+
116
+ Raises:
117
+ ValueError: If any index table has a different count
118
+ """
119
+ main_count = row[0]
120
+
121
+ if not index_tables:
122
+ return main_count
123
+
124
+ index_counts = {}
125
+ mismatch = []
126
+
127
+ for idx, index_table in enumerate(index_tables):
128
+ index_count = row[idx + 1]
129
+ index_counts[index_table] = index_count
130
+ if index_count != main_count:
131
+ mismatch.append(index_table)
132
+
133
+ # If there's a mismatch, raise an exception with details
134
+ if mismatch:
135
+ error_details = [f"Main table '{table_name}': {main_count} rows"]
136
+ for index_table, count in index_counts.items():
137
+ status = "✗ MISMATCH" if index_table in mismatch else "✓"
138
+ error_details.append(f"{status} Index '{index_table}': {count} rows")
139
+
140
+ error_msg = "Index count verification failed!\n" + "\n".join(error_details)
141
+ raise ValueError(error_msg)
142
+
143
+ return main_count
@@ -19,10 +19,11 @@ This module provides metadata scanning capabilities for MatrixOne tables,
19
19
  allowing users to analyze table statistics, column information, and data distribution.
20
20
  """
21
21
 
22
- from typing import Optional, List, Dict, Any, Union
23
- from sqlalchemy.engine import Result
24
22
  from dataclasses import dataclass
25
23
  from enum import Enum
24
+ from typing import Any, Dict, List, Optional, Union
25
+
26
+ from sqlalchemy.engine import Result
26
27
 
27
28
 
28
29
  class MetadataColumn(Enum):
@@ -40,8 +40,6 @@ from .exceptions import MatrixOneError
40
40
  class MoCtlError(MatrixOneError):
41
41
  """Raised when mo_ctl operations fail"""
42
42
 
43
- pass
44
-
45
43
 
46
44
  class MoCtlManager:
47
45
  """
@@ -147,6 +147,7 @@ __all__ = [
147
147
  "FulltextIndex",
148
148
  "FulltextAlgorithmType",
149
149
  "FulltextModeType",
150
+ "FulltextParserType",
150
151
  "FulltextSearchBuilder",
151
152
  "create_fulltext_index",
152
153
  "fulltext_search_builder",
@@ -519,8 +519,6 @@ def requires_version(
519
519
  class VersionError(MatrixOneError):
520
520
  """Raised when version compatibility check fails"""
521
521
 
522
- pass
523
-
524
522
 
525
523
  # Initialize common feature requirements
526
524
  def _initialize_default_features():
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: matrixone-python-sdk
3
- Version: 0.1.2
3
+ Version: 0.1.4
4
4
  Summary: A comprehensive Python SDK for MatrixOne database operations with vector search, fulltext search, and advanced features
5
5
  Home-page: https://github.com/matrixorigin/matrixone
6
6
  Author: MatrixOne Team
@@ -41,6 +41,7 @@ matrixone/client.py
41
41
  matrixone/config.py
42
42
  matrixone/connection_hooks.py
43
43
  matrixone/exceptions.py
44
+ matrixone/index_utils.py
44
45
  matrixone/logger.py
45
46
  matrixone/metadata.py
46
47
  matrixone/moctl.py
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "matrixone-python-sdk"
7
- version = "0.1.2"
7
+ version = "0.1.4"
8
8
  description = "A comprehensive Python SDK for MatrixOne database operations with vector search, fulltext search, and advanced features"
9
9
  readme = "README_USER.md"
10
10
  license = {text = "Apache-2.0"}