matrixone-python-sdk 0.1.15__tar.gz → 0.1.16__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 (102) hide show
  1. {matrixone_python_sdk-0.1.15/matrixone_python_sdk.egg-info → matrixone_python_sdk-0.1.16}/PKG-INFO +1 -1
  2. {matrixone_python_sdk-0.1.15 → matrixone_python_sdk-0.1.16}/README.md +13 -0
  3. matrixone_python_sdk-0.1.16/examples/example_branch_builder.py +226 -0
  4. matrixone_python_sdk-0.1.16/examples/example_clone_builder.py +54 -0
  5. {matrixone_python_sdk-0.1.15 → matrixone_python_sdk-0.1.16}/matrixone/__init__.py +24 -0
  6. matrixone_python_sdk-0.1.16/matrixone/_utils.py +37 -0
  7. {matrixone_python_sdk-0.1.15 → matrixone_python_sdk-0.1.16}/matrixone/async_client.py +22 -102
  8. {matrixone_python_sdk-0.1.15 → matrixone_python_sdk-0.1.16}/matrixone/branch.py +6 -36
  9. matrixone_python_sdk-0.1.16/matrixone/branch_builder.py +312 -0
  10. {matrixone_python_sdk-0.1.15 → matrixone_python_sdk-0.1.16}/matrixone/client.py +202 -115
  11. {matrixone_python_sdk-0.1.15 → matrixone_python_sdk-0.1.16}/matrixone/clone.py +4 -2
  12. matrixone_python_sdk-0.1.16/matrixone/clone_builder.py +138 -0
  13. {matrixone_python_sdk-0.1.15 → matrixone_python_sdk-0.1.16}/matrixone/fulltext_manager.py +15 -18
  14. {matrixone_python_sdk-0.1.15 → matrixone_python_sdk-0.1.16}/matrixone/logger.py +63 -389
  15. {matrixone_python_sdk-0.1.15 → matrixone_python_sdk-0.1.16}/matrixone/session.py +22 -24
  16. {matrixone_python_sdk-0.1.15 → matrixone_python_sdk-0.1.16}/matrixone/vector_manager.py +48 -53
  17. {matrixone_python_sdk-0.1.15 → matrixone_python_sdk-0.1.16/matrixone_python_sdk.egg-info}/PKG-INFO +1 -1
  18. {matrixone_python_sdk-0.1.15 → matrixone_python_sdk-0.1.16}/matrixone_python_sdk.egg-info/SOURCES.txt +5 -0
  19. {matrixone_python_sdk-0.1.15 → matrixone_python_sdk-0.1.16}/pyproject.toml +1 -1
  20. {matrixone_python_sdk-0.1.15 → matrixone_python_sdk-0.1.16}/LICENSE +0 -0
  21. {matrixone_python_sdk-0.1.15 → matrixone_python_sdk-0.1.16}/MANIFEST.in +0 -0
  22. {matrixone_python_sdk-0.1.15 → matrixone_python_sdk-0.1.16}/README_USER.md +0 -0
  23. {matrixone_python_sdk-0.1.15 → matrixone_python_sdk-0.1.16}/examples/example_01_basic_connection.py +0 -0
  24. {matrixone_python_sdk-0.1.15 → matrixone_python_sdk-0.1.16}/examples/example_02_account_management.py +0 -0
  25. {matrixone_python_sdk-0.1.15 → matrixone_python_sdk-0.1.16}/examples/example_03_async_operations.py +0 -0
  26. {matrixone_python_sdk-0.1.15 → matrixone_python_sdk-0.1.16}/examples/example_04_transaction_management.py +0 -0
  27. {matrixone_python_sdk-0.1.15 → matrixone_python_sdk-0.1.16}/examples/example_05_snapshot_restore.py +0 -0
  28. {matrixone_python_sdk-0.1.15 → matrixone_python_sdk-0.1.16}/examples/example_06_sqlalchemy_integration.py +0 -0
  29. {matrixone_python_sdk-0.1.15 → matrixone_python_sdk-0.1.16}/examples/example_07_advanced_features.py +0 -0
  30. {matrixone_python_sdk-0.1.15 → matrixone_python_sdk-0.1.16}/examples/example_08_pubsub_operations.py +0 -0
  31. {matrixone_python_sdk-0.1.15 → matrixone_python_sdk-0.1.16}/examples/example_09_logger_integration.py +0 -0
  32. {matrixone_python_sdk-0.1.15 → matrixone_python_sdk-0.1.16}/examples/example_10_version_management.py +0 -0
  33. {matrixone_python_sdk-0.1.15 → matrixone_python_sdk-0.1.16}/examples/example_11_matrixone_version_demo.py +0 -0
  34. {matrixone_python_sdk-0.1.15 → matrixone_python_sdk-0.1.16}/examples/example_12_vector_basics.py +0 -0
  35. {matrixone_python_sdk-0.1.15 → matrixone_python_sdk-0.1.16}/examples/example_13_vector_indexes.py +0 -0
  36. {matrixone_python_sdk-0.1.15 → matrixone_python_sdk-0.1.16}/examples/example_14_vector_search.py +0 -0
  37. {matrixone_python_sdk-0.1.15 → matrixone_python_sdk-0.1.16}/examples/example_15_vector_advanced.py +0 -0
  38. {matrixone_python_sdk-0.1.15 → matrixone_python_sdk-0.1.16}/examples/example_18_snapshot_orm.py +0 -0
  39. {matrixone_python_sdk-0.1.15 → matrixone_python_sdk-0.1.16}/examples/example_19_sqlalchemy_style_orm.py +0 -0
  40. {matrixone_python_sdk-0.1.15 → matrixone_python_sdk-0.1.16}/examples/example_20_sqlalchemy_engine_integration.py +0 -0
  41. {matrixone_python_sdk-0.1.15 → matrixone_python_sdk-0.1.16}/examples/example_21_advanced_orm_features.py +0 -0
  42. {matrixone_python_sdk-0.1.15 → matrixone_python_sdk-0.1.16}/examples/example_22_unified_sql_builder.py +0 -0
  43. {matrixone_python_sdk-0.1.15 → matrixone_python_sdk-0.1.16}/examples/example_23_load_data_operations.py +0 -0
  44. {matrixone_python_sdk-0.1.15 → matrixone_python_sdk-0.1.16}/examples/example_24_query_update.py +0 -0
  45. {matrixone_python_sdk-0.1.15 → matrixone_python_sdk-0.1.16}/examples/example_25_metadata_operations.py +0 -0
  46. {matrixone_python_sdk-0.1.15 → matrixone_python_sdk-0.1.16}/examples/example_26_stage_operations.py +0 -0
  47. {matrixone_python_sdk-0.1.15 → matrixone_python_sdk-0.1.16}/examples/example_27_export_operations.py +0 -0
  48. {matrixone_python_sdk-0.1.15 → matrixone_python_sdk-0.1.16}/examples/example_28_sqlalchemy_select.py +0 -0
  49. {matrixone_python_sdk-0.1.15 → matrixone_python_sdk-0.1.16}/examples/example_29_complex_queries.py +0 -0
  50. {matrixone_python_sdk-0.1.15 → matrixone_python_sdk-0.1.16}/examples/example_30_with_snapshot_method.py +0 -0
  51. {matrixone_python_sdk-0.1.15 → matrixone_python_sdk-0.1.16}/examples/example_31_cdc_operations.py +0 -0
  52. {matrixone_python_sdk-0.1.15 → matrixone_python_sdk-0.1.16}/examples/example_32_branch_operations.py +0 -0
  53. {matrixone_python_sdk-0.1.15 → matrixone_python_sdk-0.1.16}/examples/example_33_ivf_rank_search.py +0 -0
  54. {matrixone_python_sdk-0.1.15 → matrixone_python_sdk-0.1.16}/examples/example_34_branch_diff_count.py +0 -0
  55. {matrixone_python_sdk-0.1.15 → matrixone_python_sdk-0.1.16}/examples/example_connection_hooks.py +0 -0
  56. {matrixone_python_sdk-0.1.15 → matrixone_python_sdk-0.1.16}/examples/example_dynamic_logging.py +0 -0
  57. {matrixone_python_sdk-0.1.15 → matrixone_python_sdk-0.1.16}/examples/example_ivf_stats_complete.py +0 -0
  58. {matrixone_python_sdk-0.1.15 → matrixone_python_sdk-0.1.16}/matrixone/account.py +0 -0
  59. {matrixone_python_sdk-0.1.15 → matrixone_python_sdk-0.1.16}/matrixone/async_orm.py +0 -0
  60. {matrixone_python_sdk-0.1.15 → matrixone_python_sdk-0.1.16}/matrixone/base_client.py +0 -0
  61. {matrixone_python_sdk-0.1.15 → matrixone_python_sdk-0.1.16}/matrixone/cdc.py +0 -0
  62. {matrixone_python_sdk-0.1.15 → matrixone_python_sdk-0.1.16}/matrixone/cli_tools.py +0 -0
  63. {matrixone_python_sdk-0.1.15 → matrixone_python_sdk-0.1.16}/matrixone/config.py +0 -0
  64. {matrixone_python_sdk-0.1.15 → matrixone_python_sdk-0.1.16}/matrixone/connection_hooks.py +0 -0
  65. {matrixone_python_sdk-0.1.15 → matrixone_python_sdk-0.1.16}/matrixone/exceptions.py +0 -0
  66. {matrixone_python_sdk-0.1.15 → matrixone_python_sdk-0.1.16}/matrixone/export.py +0 -0
  67. {matrixone_python_sdk-0.1.15 → matrixone_python_sdk-0.1.16}/matrixone/index_utils.py +0 -0
  68. {matrixone_python_sdk-0.1.15 → matrixone_python_sdk-0.1.16}/matrixone/ivf_rank.py +0 -0
  69. {matrixone_python_sdk-0.1.15 → matrixone_python_sdk-0.1.16}/matrixone/load_data.py +0 -0
  70. {matrixone_python_sdk-0.1.15 → matrixone_python_sdk-0.1.16}/matrixone/metadata.py +0 -0
  71. {matrixone_python_sdk-0.1.15 → matrixone_python_sdk-0.1.16}/matrixone/moctl.py +0 -0
  72. {matrixone_python_sdk-0.1.15 → matrixone_python_sdk-0.1.16}/matrixone/orm.py +0 -0
  73. {matrixone_python_sdk-0.1.15 → matrixone_python_sdk-0.1.16}/matrixone/pitr.py +0 -0
  74. {matrixone_python_sdk-0.1.15 → matrixone_python_sdk-0.1.16}/matrixone/pubsub.py +0 -0
  75. {matrixone_python_sdk-0.1.15 → matrixone_python_sdk-0.1.16}/matrixone/restore.py +0 -0
  76. {matrixone_python_sdk-0.1.15 → matrixone_python_sdk-0.1.16}/matrixone/search_vector_index.py +0 -0
  77. {matrixone_python_sdk-0.1.15 → matrixone_python_sdk-0.1.16}/matrixone/snapshot.py +0 -0
  78. {matrixone_python_sdk-0.1.15 → matrixone_python_sdk-0.1.16}/matrixone/sql_builder.py +0 -0
  79. {matrixone_python_sdk-0.1.15 → matrixone_python_sdk-0.1.16}/matrixone/sqlalchemy_ext/__init__.py +0 -0
  80. {matrixone_python_sdk-0.1.15 → matrixone_python_sdk-0.1.16}/matrixone/sqlalchemy_ext/dialect.py +0 -0
  81. {matrixone_python_sdk-0.1.15 → matrixone_python_sdk-0.1.16}/matrixone/sqlalchemy_ext/fulltext_index.py +0 -0
  82. {matrixone_python_sdk-0.1.15 → matrixone_python_sdk-0.1.16}/matrixone/sqlalchemy_ext/fulltext_search.py +0 -0
  83. {matrixone_python_sdk-0.1.15 → matrixone_python_sdk-0.1.16}/matrixone/sqlalchemy_ext/hnsw_config.py +0 -0
  84. {matrixone_python_sdk-0.1.15 → matrixone_python_sdk-0.1.16}/matrixone/sqlalchemy_ext/ivf_config.py +0 -0
  85. {matrixone_python_sdk-0.1.15 → matrixone_python_sdk-0.1.16}/matrixone/sqlalchemy_ext/json_functions.py +0 -0
  86. {matrixone_python_sdk-0.1.15 → matrixone_python_sdk-0.1.16}/matrixone/sqlalchemy_ext/json_type.py +0 -0
  87. {matrixone_python_sdk-0.1.15 → matrixone_python_sdk-0.1.16}/matrixone/sqlalchemy_ext/snapshot.py +0 -0
  88. {matrixone_python_sdk-0.1.15 → matrixone_python_sdk-0.1.16}/matrixone/sqlalchemy_ext/table_builder.py +0 -0
  89. {matrixone_python_sdk-0.1.15 → matrixone_python_sdk-0.1.16}/matrixone/sqlalchemy_ext/vector_index.py +0 -0
  90. {matrixone_python_sdk-0.1.15 → matrixone_python_sdk-0.1.16}/matrixone/sqlalchemy_ext/vector_type.py +0 -0
  91. {matrixone_python_sdk-0.1.15 → matrixone_python_sdk-0.1.16}/matrixone/sqlalchemy_select.py +0 -0
  92. {matrixone_python_sdk-0.1.15 → matrixone_python_sdk-0.1.16}/matrixone/stage.py +0 -0
  93. {matrixone_python_sdk-0.1.15 → matrixone_python_sdk-0.1.16}/matrixone/version.py +0 -0
  94. {matrixone_python_sdk-0.1.15 → matrixone_python_sdk-0.1.16}/matrixone_python_sdk.egg-info/dependency_links.txt +0 -0
  95. {matrixone_python_sdk-0.1.15 → matrixone_python_sdk-0.1.16}/matrixone_python_sdk.egg-info/entry_points.txt +0 -0
  96. {matrixone_python_sdk-0.1.15 → matrixone_python_sdk-0.1.16}/matrixone_python_sdk.egg-info/not-zip-safe +0 -0
  97. {matrixone_python_sdk-0.1.15 → matrixone_python_sdk-0.1.16}/matrixone_python_sdk.egg-info/requires.txt +0 -0
  98. {matrixone_python_sdk-0.1.15 → matrixone_python_sdk-0.1.16}/matrixone_python_sdk.egg-info/top_level.txt +0 -0
  99. {matrixone_python_sdk-0.1.15 → matrixone_python_sdk-0.1.16}/mo_diag.py +0 -0
  100. {matrixone_python_sdk-0.1.15 → matrixone_python_sdk-0.1.16}/requirements.txt +0 -0
  101. {matrixone_python_sdk-0.1.15 → matrixone_python_sdk-0.1.16}/setup.cfg +0 -0
  102. {matrixone_python_sdk-0.1.15 → matrixone_python_sdk-0.1.16}/setup.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: matrixone-python-sdk
3
- Version: 0.1.15
3
+ Version: 0.1.16
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
@@ -56,11 +56,18 @@ A comprehensive Python SDK for MatrixOne that provides SQLAlchemy-like interface
56
56
  - 📸 **Snapshot Management**: Create and manage database snapshots at multiple levels
57
57
  - ⏰ **Point-in-Time Recovery**: PITR functionality for precise data recovery
58
58
  - 🔄 **Table Cloning**: Clone databases and tables efficiently with data replication
59
+ - ⭐ **SQLAlchemy-style statement builders** (`clone_table()`, `clone_database()`)
60
+ - Snapshot-based point-in-time cloning
61
+ - Cross-tenant cloning with TO ACCOUNT support
62
+ - IF NOT EXISTS for idempotent operations
59
63
  - 🌿 **Branch Management**: Git-style version control for databases and tables
60
64
  - Create isolated branches for development and testing
61
65
  - Compare differences between branches with count-only optimization
62
66
  - Merge branches with conflict resolution strategies
63
67
  - Point-in-time branching from snapshots
68
+ - ⭐ **SQLAlchemy-style statement builders** (`create_table_branch()`, `diff_table_branch()`, etc.)
69
+ - Advanced DIFF output: COUNT, LIMIT, FILE export, snapshot on both sides
70
+ - Cross-tenant branching with TO ACCOUNT support
64
71
  - 👥 **Account Management**: Comprehensive user, role, and permission management
65
72
  - 📊 **Pub/Sub**: Real-time publication and subscription support
66
73
  - 🔧 **Version Management**: Automatic backend version detection and compatibility checking
@@ -1384,6 +1391,12 @@ Check out the `examples/` directory for comprehensive usage examples:
1384
1391
  **Stage Management Examples:**
1385
1392
  - `example_26_stage_operations.py` - External stage management and data loading from stages
1386
1393
 
1394
+ **Branch Builder Examples:**
1395
+ - `example_branch_builder.py` - SQLAlchemy-style branch statement builders (create, diff, merge)
1396
+
1397
+ **Clone Builder Examples:**
1398
+ - `example_clone_builder.py` - SQLAlchemy-style clone statement builders (table, database)
1399
+
1387
1400
  **Specialized Examples:**
1388
1401
  - `example_connection_hooks.py` - Connection hooks for custom initialization
1389
1402
  - `example_dynamic_logging.py` - Dynamic logging configuration
@@ -0,0 +1,226 @@
1
+ """
2
+ Example: Branch Statement Builders - SQLAlchemy Style API
3
+
4
+ This example demonstrates the new branch statement builders that work like
5
+ SQLAlchemy's select(), insert(), delete(), update() functions.
6
+
7
+ No client dependency - just build SQL statements and execute them.
8
+ """
9
+
10
+ from matrixone.branch_builder import (
11
+ create_table_branch,
12
+ create_database_branch,
13
+ delete_table_branch,
14
+ delete_database_branch,
15
+ diff_table_branch,
16
+ merge_table_branch,
17
+ )
18
+
19
+
20
+ def example_create_table_branch():
21
+ """Create table branch statements"""
22
+ print("=" * 60)
23
+ print("CREATE TABLE BRANCH Examples")
24
+ print("=" * 60)
25
+
26
+ # Basic table branch
27
+ stmt = create_table_branch('users_dev').from_table('users')
28
+ print(f"Basic: {stmt}")
29
+
30
+ # With snapshot
31
+ stmt = create_table_branch('users_backup').from_table('users', snapshot='daily_snap')
32
+ print(f"With snapshot: {stmt}")
33
+
34
+ # Cross-tenant (sys only)
35
+ stmt = create_table_branch('users_dev').from_table('users').to_account('tenant1')
36
+ print(f"Cross-tenant: {stmt}")
37
+
38
+ # Full example
39
+ stmt = create_table_branch('users_historical').from_table('users', snapshot='2024_01_01').to_account('analytics_team')
40
+ print(f"Full: {stmt}\n")
41
+
42
+
43
+ def example_create_database_branch():
44
+ """Create database branch statements"""
45
+ print("=" * 60)
46
+ print("CREATE DATABASE BRANCH Examples")
47
+ print("=" * 60)
48
+
49
+ # Basic database branch
50
+ stmt = create_database_branch('dev_db').from_database('prod_db')
51
+ print(f"Basic: {stmt}")
52
+
53
+ # With snapshot
54
+ stmt = create_database_branch('backup_db').from_database('prod_db', snapshot='weekly_snap')
55
+ print(f"With snapshot: {stmt}")
56
+
57
+ # Cross-tenant
58
+ stmt = create_database_branch('dev_db').from_database('prod_db').to_account('dev_team')
59
+ print(f"Cross-tenant: {stmt}\n")
60
+
61
+
62
+ def example_delete_branch():
63
+ """Delete branch statements"""
64
+ print("=" * 60)
65
+ print("DELETE BRANCH Examples")
66
+ print("=" * 60)
67
+
68
+ # Delete table branch
69
+ stmt = delete_table_branch('users_dev')
70
+ print(f"Delete table: {stmt}")
71
+
72
+ # Delete database branch
73
+ stmt = delete_database_branch('dev_db')
74
+ print(f"Delete database: {stmt}\n")
75
+
76
+
77
+ def example_diff_branch():
78
+ """Diff branch statements"""
79
+ print("=" * 60)
80
+ print("DIFF BRANCH Examples")
81
+ print("=" * 60)
82
+
83
+ # Basic diff
84
+ stmt = diff_table_branch('users_dev').against('users')
85
+ print(f"Basic diff: {stmt}")
86
+
87
+ # Diff with snapshots
88
+ stmt = diff_table_branch('users').snapshot('snap_v1').against('users', snapshot='snap_v2')
89
+ print(f"With snapshots: {stmt}")
90
+
91
+ # Diff with output count
92
+ stmt = diff_table_branch('users_dev').against('users').output_count()
93
+ print(f"Output count: {stmt}")
94
+
95
+ # Diff with output limit
96
+ stmt = diff_table_branch('users_dev').against('users').output_limit(100)
97
+ print(f"Output limit: {stmt}")
98
+
99
+ # Diff with file export (local)
100
+ stmt = diff_table_branch('users_dev').against('users').output_file('/tmp/diff.sql')
101
+ print(f"Export to file: {stmt}")
102
+
103
+ # Diff with file export (stage)
104
+ stmt = diff_table_branch('users_dev').against('users').output_file('stage://backup_stage/')
105
+ print(f"Export to stage: {stmt}")
106
+
107
+ # Diff with output as table
108
+ stmt = diff_table_branch('users_dev').against('users').output_as('diff_result')
109
+ print(f"Output as table: {stmt}\n")
110
+
111
+
112
+ def example_merge_branch():
113
+ """Merge branch statements"""
114
+ print("=" * 60)
115
+ print("MERGE BRANCH Examples")
116
+ print("=" * 60)
117
+
118
+ # Basic merge (default: skip conflicts)
119
+ stmt = merge_table_branch('users_dev').into('users')
120
+ print(f"Basic merge (skip): {stmt}")
121
+
122
+ # Merge with accept strategy
123
+ stmt = merge_table_branch('users_dev').into('users').when_conflict('accept')
124
+ print(f"Merge (accept): {stmt}")
125
+
126
+ # Merge with enum
127
+ from matrixone.branch_builder import MergeConflictStrategy
128
+
129
+ stmt = merge_table_branch('users_dev').into('users').when_conflict(MergeConflictStrategy.ACCEPT)
130
+ print(f"Merge with enum: {stmt}\n")
131
+
132
+
133
+ def example_with_session():
134
+ """Example of using statements with session"""
135
+ print("=" * 60)
136
+ print("Using with Session")
137
+ print("=" * 60)
138
+
139
+ # Build statements
140
+ create_stmt = create_table_branch('users_dev').from_table('users')
141
+ diff_stmt = diff_table_branch('users_dev').against('users').output_count()
142
+ merge_stmt = merge_table_branch('users_dev').into('users')
143
+
144
+ print("Statements ready to execute:")
145
+ print(f" 1. {create_stmt}")
146
+ print(f" 2. {diff_stmt}")
147
+ print(f" 3. {merge_stmt}")
148
+
149
+ print("\nUsage with session:")
150
+ print("""
151
+ from matrixone import Client
152
+
153
+ client = Client()
154
+ client.connect(database='test')
155
+
156
+ # Execute statements
157
+ with client.session() as session:
158
+ # Create branch
159
+ session.execute(create_table_branch('users_dev').from_table('users'))
160
+
161
+ # Check differences
162
+ result = session.execute(
163
+ diff_table_branch('users_dev').against('users').output_count()
164
+ )
165
+ print(f"Differences: {result.fetchone()}")
166
+
167
+ # Merge back
168
+ session.execute(
169
+ merge_table_branch('users_dev').into('users').when_conflict('accept')
170
+ )
171
+ """)
172
+
173
+
174
+ def example_complex_workflow():
175
+ """Complex workflow example"""
176
+ print("=" * 60)
177
+ print("Complex Workflow Example")
178
+ print("=" * 60)
179
+
180
+ print("""
181
+ # Development workflow with branches
182
+
183
+ # 1. Create development branch from production
184
+ create_dev = create_table_branch('orders_dev').from_table('orders')
185
+
186
+ # 2. Create testing branch from development
187
+ create_test = create_table_branch('orders_test').from_table('orders_dev')
188
+
189
+ # 3. Compare test with production
190
+ diff_test = (diff_table_branch('orders_test')
191
+ .against('orders')
192
+ .output_file('stage://reports/test_diff.sql'))
193
+
194
+ # 4. Merge test back to production
195
+ merge_prod = (merge_table_branch('orders_test')
196
+ .into('orders')
197
+ .when_conflict('accept'))
198
+
199
+ # 5. Clean up development branch
200
+ delete_dev = delete_table_branch('orders_dev')
201
+
202
+ # Execute in transaction
203
+ with client.session() as session:
204
+ session.execute(create_dev)
205
+ session.execute(create_test)
206
+
207
+ # Verify differences
208
+ result = session.execute(
209
+ diff_table_branch('orders_test').against('orders').output_count()
210
+ )
211
+
212
+ if result.fetchone()[0] == 0:
213
+ # No differences, safe to merge
214
+ session.execute(merge_prod)
215
+ session.execute(delete_dev)
216
+ """)
217
+
218
+
219
+ if __name__ == '__main__':
220
+ example_create_table_branch()
221
+ example_create_database_branch()
222
+ example_delete_branch()
223
+ example_diff_branch()
224
+ example_merge_branch()
225
+ example_with_session()
226
+ example_complex_workflow()
@@ -0,0 +1,54 @@
1
+ """
2
+ Example: Clone Statement Builders - SQLAlchemy Style API
3
+
4
+ Demonstrates the clone statement builders that produce SQL strings
5
+ independently of the client, similar to SQLAlchemy's select()/insert().
6
+ """
7
+
8
+ from matrixone.clone_builder import clone_table, clone_database
9
+
10
+
11
+ def example_clone_table():
12
+ print("=" * 60)
13
+ print("CLONE TABLE Examples")
14
+ print("=" * 60)
15
+
16
+ stmt = clone_table('users_copy').from_table('users')
17
+ print(f"Basic: {stmt}")
18
+
19
+ stmt = clone_table('users_copy').if_not_exists().from_table('users')
20
+ print(f"If not exists: {stmt}")
21
+
22
+ stmt = clone_table('users_copy').from_table('users', snapshot='snap1')
23
+ print(f"With snapshot: {stmt}")
24
+
25
+ stmt = clone_table('db2.users').from_table('db1.users')
26
+ print(f"Cross-database: {stmt}")
27
+
28
+ stmt = clone_table('users_copy').if_not_exists().from_table('users', snapshot='snap1').to_account('tenant1')
29
+ print(f"Cross-tenant: {stmt}")
30
+ print()
31
+
32
+
33
+ def example_clone_database():
34
+ print("=" * 60)
35
+ print("CLONE DATABASE Examples")
36
+ print("=" * 60)
37
+
38
+ stmt = clone_database('dev_db').from_database('prod_db')
39
+ print(f"Basic: {stmt}")
40
+
41
+ stmt = clone_database('dev_db').if_not_exists().from_database('prod_db')
42
+ print(f"If not exists: {stmt}")
43
+
44
+ stmt = clone_database('dev_db').from_database('prod_db', snapshot='daily_snap')
45
+ print(f"With snapshot: {stmt}")
46
+
47
+ stmt = clone_database('dev_db').from_database('prod_db', snapshot='snap1').to_account('acc1')
48
+ print(f"Cross-tenant: {stmt}")
49
+ print()
50
+
51
+
52
+ if __name__ == '__main__':
53
+ example_clone_table()
54
+ example_clone_database()
@@ -57,6 +57,19 @@ from .restore import RestoreManager
57
57
  from .snapshot import Snapshot, SnapshotLevel, SnapshotManager
58
58
  from .clone import CloneManager
59
59
  from .branch import BranchManager, AsyncBranchManager, DiffOutput, MergeConflictStrategy
60
+ from .branch_builder import (
61
+ create_table_branch,
62
+ create_database_branch,
63
+ delete_table_branch,
64
+ delete_database_branch,
65
+ diff_table_branch,
66
+ merge_table_branch,
67
+ DiffOutputOption,
68
+ )
69
+ from .clone_builder import (
70
+ clone_table,
71
+ clone_database,
72
+ )
60
73
  from .stage import Stage, StageManager, AsyncStageManager
61
74
  from .cdc import CDCTaskInfo, CDCWatermarkInfo, CDCManager, AsyncCDCManager, build_mysql_uri
62
75
  from .ivf_rank import IVFRankMode, IVFRankOptions
@@ -122,6 +135,17 @@ __all__ = [
122
135
  "CloneManager",
123
136
  "BranchManager",
124
137
  "AsyncBranchManager",
138
+ "DiffOutput",
139
+ "MergeConflictStrategy",
140
+ "create_table_branch",
141
+ "create_database_branch",
142
+ "delete_table_branch",
143
+ "delete_database_branch",
144
+ "diff_table_branch",
145
+ "merge_table_branch",
146
+ "DiffOutputOption",
147
+ "clone_table",
148
+ "clone_database",
125
149
  "SnapshotLevel",
126
150
  "MoCtlManager",
127
151
  "RestoreManager",
@@ -0,0 +1,37 @@
1
+ """Shared internal utilities for MatrixOne SDK modules."""
2
+
3
+ from typing import Union, Type
4
+
5
+ try:
6
+ from sqlalchemy.orm import DeclarativeMeta as _unused # noqa: F401
7
+
8
+ SQLALCHEMY_AVAILABLE = True
9
+ except ImportError:
10
+ SQLALCHEMY_AVAILABLE = False
11
+
12
+
13
+ def get_table_name(table: Union[str, Type]) -> str:
14
+ """Extract table name from string or ORM model.
15
+
16
+ Supports:
17
+ - "table_name"
18
+ - "db.table_name"
19
+ - ORM model with optional __table_args__['schema'] for db.table
20
+ """
21
+ if isinstance(table, str):
22
+ return table
23
+ if SQLALCHEMY_AVAILABLE and hasattr(table, '__tablename__'):
24
+ name = table.__tablename__
25
+ if hasattr(table, '__table_args__'):
26
+ args = table.__table_args__
27
+ if isinstance(args, dict) and 'schema' in args:
28
+ return f"{args['schema']}.{name}"
29
+ return name
30
+ raise ValueError(f"Invalid table parameter: {table}. Expected string or ORM model.")
31
+
32
+
33
+ def require_non_empty(value: str, param_name: str) -> str:
34
+ """Validate that a string parameter is non-empty."""
35
+ if not value:
36
+ raise ValueError(f"{param_name} must be a non-empty string")
37
+ return value
@@ -1019,9 +1019,7 @@ class AsyncClient(BaseMatrixOneClient):
1019
1019
 
1020
1020
  return self
1021
1021
 
1022
- async def _execute_with_logging(
1023
- self, connection, sql: str, context: str = "Async SQL execution", override_sql_log_mode: str = None
1024
- ):
1022
+ async def _execute_with_logging(self, connection, sql: str, context: str = "Async SQL execution", log_mode: str = None):
1025
1023
  """
1026
1024
  Execute SQL asynchronously with proper logging through the client's logger.
1027
1025
 
@@ -1033,7 +1031,7 @@ class AsyncClient(BaseMatrixOneClient):
1033
1031
  connection: SQLAlchemy async connection object
1034
1032
  sql: SQL query string
1035
1033
  context: Context description for error logging (default: "Async SQL execution")
1036
- override_sql_log_mode: Temporarily override sql_log_mode for this query only
1034
+ log_mode: Temporarily override sql_log_mode for this query only
1037
1035
 
1038
1036
  Returns::
1039
1037
 
@@ -1058,26 +1056,24 @@ class AsyncClient(BaseMatrixOneClient):
1058
1056
  if result.returns_rows:
1059
1057
  # For SELECT queries, we can't consume the result to count rows
1060
1058
  # So we just log without row count
1061
- self.logger.log_query(
1062
- sql, execution_time, None, success=True, override_sql_log_mode=override_sql_log_mode
1063
- )
1059
+ self.logger.log_query(sql, execution_time, None, success=True, log_mode=log_mode)
1064
1060
  else:
1065
1061
  # For DML queries (INSERT/UPDATE/DELETE), we can get rowcount
1066
- self.logger.log_query(
1067
- sql, execution_time, result.rowcount, success=True, override_sql_log_mode=override_sql_log_mode
1068
- )
1062
+ self.logger.log_query(sql, execution_time, result.rowcount, success=True, log_mode=log_mode)
1069
1063
  except Exception:
1070
1064
  # Fallback: just log the query without row count
1071
- self.logger.log_query(sql, execution_time, None, success=True, override_sql_log_mode=override_sql_log_mode)
1065
+ self.logger.log_query(sql, execution_time, None, success=True, log_mode=log_mode)
1072
1066
 
1073
1067
  return result
1074
1068
  except Exception as e:
1075
1069
  execution_time = time.time() - start_time
1076
- self.logger.log_query(sql, execution_time, success=False, override_sql_log_mode=override_sql_log_mode)
1070
+ self.logger.log_query(sql, execution_time, success=False, log_mode=log_mode)
1077
1071
  self.logger.log_error(e, context=context)
1078
- raise
1072
+ from .client import _classify_db_error
1079
1073
 
1080
- async def execute(self, sql_or_stmt, params: Optional[Tuple] = None, _log_mode: str = None) -> AsyncResultSet:
1074
+ raise _classify_db_error(e, sql) from None
1075
+
1076
+ async def execute(self, sql_or_stmt, params: Optional[Tuple] = None, log_mode: str = None) -> AsyncResultSet:
1081
1077
  """
1082
1078
  Execute SQL query or SQLAlchemy statement asynchronously without transaction isolation.
1083
1079
 
@@ -1111,7 +1107,7 @@ class AsyncClient(BaseMatrixOneClient):
1111
1107
  substituted for '?' placeholders in order. Automatically escaped to prevent
1112
1108
  SQL injection. Ignored for SQLAlchemy statements.
1113
1109
 
1114
- _log_mode (Optional[str]): Override SQL logging mode for this query only.
1110
+ log_mode (Optional[str]): Override SQL logging mode for this query only.
1115
1111
  Options: 'off', 'simple', 'full'. If None, uses client's global sql_log_mode
1116
1112
  setting. Useful for debugging or disabling logs for frequently-executed queries.
1117
1113
 
@@ -1297,13 +1293,13 @@ class AsyncClient(BaseMatrixOneClient):
1297
1293
  # Disable logging for frequently executed query
1298
1294
  result = await client.execute(
1299
1295
  select(User).where(User.id == 1),
1300
- _log_mode='off'
1296
+ log_mode='off'
1301
1297
  )
1302
1298
 
1303
1299
  # Force full SQL logging for debugging
1304
1300
  result = await client.execute(
1305
1301
  select(User).where(User.name.like('%test%')),
1306
- _log_mode='full'
1302
+ log_mode='full'
1307
1303
  )
1308
1304
 
1309
1305
  await client.disconnect()
@@ -1325,7 +1321,7 @@ class AsyncClient(BaseMatrixOneClient):
1325
1321
  2. **Use parameters**: Always use parameter binding to prevent SQL injection
1326
1322
  3. **Session for transactions**: Use client.session() for atomic operations
1327
1323
  4. **Use asyncio.gather()**: For concurrent independent queries
1328
- 5. **Disable logging in production**: Use _log_mode='off' for hot paths
1324
+ 5. **Disable logging in production**: Use log_mode='off' for hot paths
1329
1325
  6. **Handle exceptions**: Wrap execute() in try-except for error handling
1330
1326
 
1331
1327
  See Also:
@@ -1346,7 +1342,8 @@ class AsyncClient(BaseMatrixOneClient):
1346
1342
  if not isinstance(sql_or_stmt, str):
1347
1343
  # SQLAlchemy statement - delegate to session for consistent behavior
1348
1344
  async with self.session() as session:
1349
- result = await session.execute(sql_or_stmt, params)
1345
+ # Suppress session-level logging; AsyncClient logs below
1346
+ result = await session.execute(sql_or_stmt, params, log_mode='off')
1350
1347
 
1351
1348
  # Convert SQLAlchemy result to AsyncResultSet
1352
1349
  if hasattr(result, 'returns_rows') and result.returns_rows:
@@ -1360,7 +1357,7 @@ class AsyncClient(BaseMatrixOneClient):
1360
1357
  execution_time,
1361
1358
  len(rows),
1362
1359
  success=True,
1363
- override_sql_log_mode=_log_mode,
1360
+ log_mode=log_mode,
1364
1361
  )
1365
1362
  return async_result
1366
1363
  else:
@@ -1372,7 +1369,7 @@ class AsyncClient(BaseMatrixOneClient):
1372
1369
  execution_time,
1373
1370
  result.rowcount,
1374
1371
  success=True,
1375
- override_sql_log_mode=_log_mode,
1372
+ log_mode=log_mode,
1376
1373
  )
1377
1374
  return async_result
1378
1375
 
@@ -1398,15 +1395,11 @@ class AsyncClient(BaseMatrixOneClient):
1398
1395
  rows = result.fetchall()
1399
1396
  columns = list(result.keys()) if hasattr(result, "keys") else []
1400
1397
  async_result = AsyncResultSet(columns, rows)
1401
- self.logger.log_query(
1402
- sql_or_stmt, execution_time, len(rows), success=True, override_sql_log_mode=_log_mode
1403
- )
1398
+ self.logger.log_query(sql_or_stmt, execution_time, len(rows), success=True, log_mode=log_mode)
1404
1399
  return async_result
1405
1400
  else:
1406
1401
  async_result = AsyncResultSet([], [], affected_rows=result.rowcount)
1407
- self.logger.log_query(
1408
- sql_or_stmt, execution_time, result.rowcount, success=True, override_sql_log_mode=_log_mode
1409
- )
1402
+ self.logger.log_query(sql_or_stmt, execution_time, result.rowcount, success=True, log_mode=log_mode)
1410
1403
  return async_result
1411
1404
 
1412
1405
  except Exception as e:
@@ -1424,82 +1417,9 @@ class AsyncClient(BaseMatrixOneClient):
1424
1417
 
1425
1418
  print(f"Warning: Error logging failed: {log_err}", file=sys.stderr)
1426
1419
 
1427
- # Extract user-friendly error message
1428
- error_msg = str(e)
1420
+ from .client import _classify_db_error
1429
1421
 
1430
- # Handle common database errors with helpful messages
1431
- # Check for "does not exist" first before "syntax error"
1432
- if (
1433
- 'does not exist' in error_msg.lower()
1434
- or 'no such table' in error_msg.lower()
1435
- or 'doesn\'t exist' in error_msg.lower()
1436
- ):
1437
- # Table doesn't exist
1438
- import re
1439
-
1440
- match = re.search(r"(?:table|database)\s+[\"']?(\w+)[\"']?\s+does not exist", error_msg, re.IGNORECASE)
1441
- if match:
1442
- obj_name = match.group(1)
1443
- raise QueryError(
1444
- f"Table or database '{obj_name}' does not exist. "
1445
- f"Create it first using client.create_table() or CREATE TABLE/DATABASE statement."
1446
- ) from None
1447
- else:
1448
- raise QueryError(f"Object not found: {error_msg}") from None
1449
-
1450
- elif 'already exists' in error_msg.lower() and '1050' in error_msg:
1451
- # Table already exists
1452
- import re
1453
-
1454
- match = re.search(r"table\s+(\w+)\s+already\s+exists", error_msg, re.IGNORECASE)
1455
- if match:
1456
- table_name = match.group(1)
1457
- raise QueryError(
1458
- f"Table '{table_name}' already exists. "
1459
- f"Use DROP TABLE {table_name} or client.drop_table() to remove it first."
1460
- ) from None
1461
- else:
1462
- raise QueryError(f"Object already exists: {error_msg}") from None
1463
-
1464
- elif 'duplicate' in error_msg.lower() and ('1062' in error_msg or '1061' in error_msg):
1465
- # Duplicate key/entry
1466
- raise QueryError(
1467
- f"Duplicate entry error: {error_msg}. "
1468
- f"Check for duplicate primary key or unique constraint violations."
1469
- ) from None
1470
-
1471
- elif 'syntax error' in error_msg.lower() or '1064' in error_msg:
1472
- # SQL syntax error
1473
- sql_preview = final_sql[:200] + '...' if len(final_sql) > 200 else final_sql
1474
- raise QueryError(f"SQL syntax error: {error_msg}\n" f"Query: {sql_preview}") from None
1475
-
1476
- elif 'column' in error_msg.lower() and ('unknown' in error_msg.lower() or 'not found' in error_msg.lower()):
1477
- # Column doesn't exist
1478
- raise QueryError(f"Column not found: {error_msg}. " f"Check your column names and table schema.") from None
1479
-
1480
- elif 'cannot be null' in error_msg.lower() or '1048' in error_msg:
1481
- # NULL constraint violation
1482
- raise QueryError(
1483
- f"NULL constraint violation: {error_msg}. " f"Some columns require non-NULL values."
1484
- ) from None
1485
-
1486
- elif 'not supported' in error_msg.lower() and '20105' in error_msg:
1487
- # MatrixOne-specific: feature not supported
1488
- raise QueryError(
1489
- f"MatrixOne feature limitation: {error_msg}. "
1490
- f"This feature may require additional configuration or is not yet supported."
1491
- ) from None
1492
-
1493
- elif 'bind parameter' in error_msg.lower() or 'InvalidRequestError' in error_msg:
1494
- # SQLAlchemy bind parameter error
1495
- raise QueryError(
1496
- f"Parameter binding error: {error_msg}. "
1497
- f"This might be caused by special characters in your data (colons in JSON, etc.)"
1498
- ) from None
1499
-
1500
- else:
1501
- # Generic error - cleaner message without full SQLAlchemy stack
1502
- raise QueryError(f"Query execution failed: {error_msg}") from None
1422
+ raise _classify_db_error(e, sql_or_stmt) from None
1503
1423
 
1504
1424
  def _substitute_parameters(self, sql: str, params: Optional[Tuple] = None) -> str:
1505
1425
  """
@@ -23,14 +23,7 @@ from enum import Enum
23
23
 
24
24
  from .exceptions import BranchError, ConnectionError
25
25
  from .version import requires_version
26
-
27
- try:
28
- from sqlalchemy.orm import DeclarativeMeta
29
-
30
- SQLALCHEMY_AVAILABLE = True
31
- except ImportError:
32
- SQLALCHEMY_AVAILABLE = False
33
- DeclarativeMeta = type # Fallback type
26
+ from ._utils import get_table_name
34
27
 
35
28
 
36
29
  class DiffOutput(str, Enum):
@@ -73,34 +66,11 @@ class BaseBranchManager:
73
66
  return self.executor if self.executor else self.client
74
67
 
75
68
  def _get_table_name(self, table: Union[str, Type]) -> str:
76
- """
77
- Extract table name from string or ORM model.
78
-
79
- Supports:
80
- - "table_name"
81
- - "db.table_name"
82
- - TableModel (SQLAlchemy ORM)
83
-
84
- Args:
85
- table: Table name string or ORM model class
86
-
87
- Returns:
88
- Fully qualified table name string
89
- """
90
- if isinstance(table, str):
91
- return table
92
-
93
- # Handle SQLAlchemy ORM model
94
- if SQLALCHEMY_AVAILABLE and hasattr(table, '__tablename__'):
95
- table_name = table.__tablename__
96
- # Check if model has __table_args__ with schema
97
- if hasattr(table, '__table_args__'):
98
- table_args = table.__table_args__
99
- if isinstance(table_args, dict) and 'schema' in table_args:
100
- return f"{table_args['schema']}.{table_name}"
101
- return table_name
102
-
103
- raise BranchError(f"Invalid table parameter: {table}. Expected string or ORM model.")
69
+ """Extract table name from string or ORM model."""
70
+ try:
71
+ return get_table_name(table)
72
+ except ValueError as e:
73
+ raise BranchError(str(e)) from e
104
74
 
105
75
  def _build_create_table_branch_sql(
106
76
  self,