matrixone-python-sdk 0.1.14__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.14/matrixone_python_sdk.egg-info → matrixone_python_sdk-0.1.16}/PKG-INFO +1 -1
  2. {matrixone_python_sdk-0.1.14 → 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.14 → 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.14 → matrixone_python_sdk-0.1.16}/matrixone/async_client.py +35 -111
  8. {matrixone_python_sdk-0.1.14 → 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.14 → matrixone_python_sdk-0.1.16}/matrixone/client.py +222 -128
  11. {matrixone_python_sdk-0.1.14 → 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.14 → matrixone_python_sdk-0.1.16}/matrixone/fulltext_manager.py +15 -18
  14. {matrixone_python_sdk-0.1.14 → matrixone_python_sdk-0.1.16}/matrixone/logger.py +63 -389
  15. {matrixone_python_sdk-0.1.14 → matrixone_python_sdk-0.1.16}/matrixone/session.py +22 -24
  16. {matrixone_python_sdk-0.1.14 → matrixone_python_sdk-0.1.16}/matrixone/sqlalchemy_ext/dialect.py +26 -1
  17. {matrixone_python_sdk-0.1.14 → matrixone_python_sdk-0.1.16}/matrixone/vector_manager.py +48 -53
  18. {matrixone_python_sdk-0.1.14 → matrixone_python_sdk-0.1.16/matrixone_python_sdk.egg-info}/PKG-INFO +1 -1
  19. {matrixone_python_sdk-0.1.14 → matrixone_python_sdk-0.1.16}/matrixone_python_sdk.egg-info/SOURCES.txt +5 -0
  20. {matrixone_python_sdk-0.1.14 → matrixone_python_sdk-0.1.16}/pyproject.toml +1 -1
  21. {matrixone_python_sdk-0.1.14 → matrixone_python_sdk-0.1.16}/LICENSE +0 -0
  22. {matrixone_python_sdk-0.1.14 → matrixone_python_sdk-0.1.16}/MANIFEST.in +0 -0
  23. {matrixone_python_sdk-0.1.14 → matrixone_python_sdk-0.1.16}/README_USER.md +0 -0
  24. {matrixone_python_sdk-0.1.14 → matrixone_python_sdk-0.1.16}/examples/example_01_basic_connection.py +0 -0
  25. {matrixone_python_sdk-0.1.14 → matrixone_python_sdk-0.1.16}/examples/example_02_account_management.py +0 -0
  26. {matrixone_python_sdk-0.1.14 → matrixone_python_sdk-0.1.16}/examples/example_03_async_operations.py +0 -0
  27. {matrixone_python_sdk-0.1.14 → matrixone_python_sdk-0.1.16}/examples/example_04_transaction_management.py +0 -0
  28. {matrixone_python_sdk-0.1.14 → matrixone_python_sdk-0.1.16}/examples/example_05_snapshot_restore.py +0 -0
  29. {matrixone_python_sdk-0.1.14 → matrixone_python_sdk-0.1.16}/examples/example_06_sqlalchemy_integration.py +0 -0
  30. {matrixone_python_sdk-0.1.14 → matrixone_python_sdk-0.1.16}/examples/example_07_advanced_features.py +0 -0
  31. {matrixone_python_sdk-0.1.14 → matrixone_python_sdk-0.1.16}/examples/example_08_pubsub_operations.py +0 -0
  32. {matrixone_python_sdk-0.1.14 → matrixone_python_sdk-0.1.16}/examples/example_09_logger_integration.py +0 -0
  33. {matrixone_python_sdk-0.1.14 → matrixone_python_sdk-0.1.16}/examples/example_10_version_management.py +0 -0
  34. {matrixone_python_sdk-0.1.14 → matrixone_python_sdk-0.1.16}/examples/example_11_matrixone_version_demo.py +0 -0
  35. {matrixone_python_sdk-0.1.14 → matrixone_python_sdk-0.1.16}/examples/example_12_vector_basics.py +0 -0
  36. {matrixone_python_sdk-0.1.14 → matrixone_python_sdk-0.1.16}/examples/example_13_vector_indexes.py +0 -0
  37. {matrixone_python_sdk-0.1.14 → matrixone_python_sdk-0.1.16}/examples/example_14_vector_search.py +0 -0
  38. {matrixone_python_sdk-0.1.14 → matrixone_python_sdk-0.1.16}/examples/example_15_vector_advanced.py +0 -0
  39. {matrixone_python_sdk-0.1.14 → matrixone_python_sdk-0.1.16}/examples/example_18_snapshot_orm.py +0 -0
  40. {matrixone_python_sdk-0.1.14 → matrixone_python_sdk-0.1.16}/examples/example_19_sqlalchemy_style_orm.py +0 -0
  41. {matrixone_python_sdk-0.1.14 → matrixone_python_sdk-0.1.16}/examples/example_20_sqlalchemy_engine_integration.py +0 -0
  42. {matrixone_python_sdk-0.1.14 → matrixone_python_sdk-0.1.16}/examples/example_21_advanced_orm_features.py +0 -0
  43. {matrixone_python_sdk-0.1.14 → matrixone_python_sdk-0.1.16}/examples/example_22_unified_sql_builder.py +0 -0
  44. {matrixone_python_sdk-0.1.14 → matrixone_python_sdk-0.1.16}/examples/example_23_load_data_operations.py +0 -0
  45. {matrixone_python_sdk-0.1.14 → matrixone_python_sdk-0.1.16}/examples/example_24_query_update.py +0 -0
  46. {matrixone_python_sdk-0.1.14 → matrixone_python_sdk-0.1.16}/examples/example_25_metadata_operations.py +0 -0
  47. {matrixone_python_sdk-0.1.14 → matrixone_python_sdk-0.1.16}/examples/example_26_stage_operations.py +0 -0
  48. {matrixone_python_sdk-0.1.14 → matrixone_python_sdk-0.1.16}/examples/example_27_export_operations.py +0 -0
  49. {matrixone_python_sdk-0.1.14 → matrixone_python_sdk-0.1.16}/examples/example_28_sqlalchemy_select.py +0 -0
  50. {matrixone_python_sdk-0.1.14 → matrixone_python_sdk-0.1.16}/examples/example_29_complex_queries.py +0 -0
  51. {matrixone_python_sdk-0.1.14 → matrixone_python_sdk-0.1.16}/examples/example_30_with_snapshot_method.py +0 -0
  52. {matrixone_python_sdk-0.1.14 → matrixone_python_sdk-0.1.16}/examples/example_31_cdc_operations.py +0 -0
  53. {matrixone_python_sdk-0.1.14 → matrixone_python_sdk-0.1.16}/examples/example_32_branch_operations.py +0 -0
  54. {matrixone_python_sdk-0.1.14 → matrixone_python_sdk-0.1.16}/examples/example_33_ivf_rank_search.py +0 -0
  55. {matrixone_python_sdk-0.1.14 → matrixone_python_sdk-0.1.16}/examples/example_34_branch_diff_count.py +0 -0
  56. {matrixone_python_sdk-0.1.14 → matrixone_python_sdk-0.1.16}/examples/example_connection_hooks.py +0 -0
  57. {matrixone_python_sdk-0.1.14 → matrixone_python_sdk-0.1.16}/examples/example_dynamic_logging.py +0 -0
  58. {matrixone_python_sdk-0.1.14 → matrixone_python_sdk-0.1.16}/examples/example_ivf_stats_complete.py +0 -0
  59. {matrixone_python_sdk-0.1.14 → matrixone_python_sdk-0.1.16}/matrixone/account.py +0 -0
  60. {matrixone_python_sdk-0.1.14 → matrixone_python_sdk-0.1.16}/matrixone/async_orm.py +0 -0
  61. {matrixone_python_sdk-0.1.14 → matrixone_python_sdk-0.1.16}/matrixone/base_client.py +0 -0
  62. {matrixone_python_sdk-0.1.14 → matrixone_python_sdk-0.1.16}/matrixone/cdc.py +0 -0
  63. {matrixone_python_sdk-0.1.14 → matrixone_python_sdk-0.1.16}/matrixone/cli_tools.py +0 -0
  64. {matrixone_python_sdk-0.1.14 → matrixone_python_sdk-0.1.16}/matrixone/config.py +0 -0
  65. {matrixone_python_sdk-0.1.14 → matrixone_python_sdk-0.1.16}/matrixone/connection_hooks.py +0 -0
  66. {matrixone_python_sdk-0.1.14 → matrixone_python_sdk-0.1.16}/matrixone/exceptions.py +0 -0
  67. {matrixone_python_sdk-0.1.14 → matrixone_python_sdk-0.1.16}/matrixone/export.py +0 -0
  68. {matrixone_python_sdk-0.1.14 → matrixone_python_sdk-0.1.16}/matrixone/index_utils.py +0 -0
  69. {matrixone_python_sdk-0.1.14 → matrixone_python_sdk-0.1.16}/matrixone/ivf_rank.py +0 -0
  70. {matrixone_python_sdk-0.1.14 → matrixone_python_sdk-0.1.16}/matrixone/load_data.py +0 -0
  71. {matrixone_python_sdk-0.1.14 → matrixone_python_sdk-0.1.16}/matrixone/metadata.py +0 -0
  72. {matrixone_python_sdk-0.1.14 → matrixone_python_sdk-0.1.16}/matrixone/moctl.py +0 -0
  73. {matrixone_python_sdk-0.1.14 → matrixone_python_sdk-0.1.16}/matrixone/orm.py +0 -0
  74. {matrixone_python_sdk-0.1.14 → matrixone_python_sdk-0.1.16}/matrixone/pitr.py +0 -0
  75. {matrixone_python_sdk-0.1.14 → matrixone_python_sdk-0.1.16}/matrixone/pubsub.py +0 -0
  76. {matrixone_python_sdk-0.1.14 → matrixone_python_sdk-0.1.16}/matrixone/restore.py +0 -0
  77. {matrixone_python_sdk-0.1.14 → matrixone_python_sdk-0.1.16}/matrixone/search_vector_index.py +0 -0
  78. {matrixone_python_sdk-0.1.14 → matrixone_python_sdk-0.1.16}/matrixone/snapshot.py +0 -0
  79. {matrixone_python_sdk-0.1.14 → matrixone_python_sdk-0.1.16}/matrixone/sql_builder.py +0 -0
  80. {matrixone_python_sdk-0.1.14 → matrixone_python_sdk-0.1.16}/matrixone/sqlalchemy_ext/__init__.py +0 -0
  81. {matrixone_python_sdk-0.1.14 → matrixone_python_sdk-0.1.16}/matrixone/sqlalchemy_ext/fulltext_index.py +0 -0
  82. {matrixone_python_sdk-0.1.14 → matrixone_python_sdk-0.1.16}/matrixone/sqlalchemy_ext/fulltext_search.py +0 -0
  83. {matrixone_python_sdk-0.1.14 → matrixone_python_sdk-0.1.16}/matrixone/sqlalchemy_ext/hnsw_config.py +0 -0
  84. {matrixone_python_sdk-0.1.14 → matrixone_python_sdk-0.1.16}/matrixone/sqlalchemy_ext/ivf_config.py +0 -0
  85. {matrixone_python_sdk-0.1.14 → matrixone_python_sdk-0.1.16}/matrixone/sqlalchemy_ext/json_functions.py +0 -0
  86. {matrixone_python_sdk-0.1.14 → matrixone_python_sdk-0.1.16}/matrixone/sqlalchemy_ext/json_type.py +0 -0
  87. {matrixone_python_sdk-0.1.14 → matrixone_python_sdk-0.1.16}/matrixone/sqlalchemy_ext/snapshot.py +0 -0
  88. {matrixone_python_sdk-0.1.14 → matrixone_python_sdk-0.1.16}/matrixone/sqlalchemy_ext/table_builder.py +0 -0
  89. {matrixone_python_sdk-0.1.14 → matrixone_python_sdk-0.1.16}/matrixone/sqlalchemy_ext/vector_index.py +0 -0
  90. {matrixone_python_sdk-0.1.14 → matrixone_python_sdk-0.1.16}/matrixone/sqlalchemy_ext/vector_type.py +0 -0
  91. {matrixone_python_sdk-0.1.14 → matrixone_python_sdk-0.1.16}/matrixone/sqlalchemy_select.py +0 -0
  92. {matrixone_python_sdk-0.1.14 → matrixone_python_sdk-0.1.16}/matrixone/stage.py +0 -0
  93. {matrixone_python_sdk-0.1.14 → matrixone_python_sdk-0.1.16}/matrixone/version.py +0 -0
  94. {matrixone_python_sdk-0.1.14 → matrixone_python_sdk-0.1.16}/matrixone_python_sdk.egg-info/dependency_links.txt +0 -0
  95. {matrixone_python_sdk-0.1.14 → matrixone_python_sdk-0.1.16}/matrixone_python_sdk.egg-info/entry_points.txt +0 -0
  96. {matrixone_python_sdk-0.1.14 → matrixone_python_sdk-0.1.16}/matrixone_python_sdk.egg-info/not-zip-safe +0 -0
  97. {matrixone_python_sdk-0.1.14 → matrixone_python_sdk-0.1.16}/matrixone_python_sdk.egg-info/requires.txt +0 -0
  98. {matrixone_python_sdk-0.1.14 → matrixone_python_sdk-0.1.16}/matrixone_python_sdk.egg-info/top_level.txt +0 -0
  99. {matrixone_python_sdk-0.1.14 → matrixone_python_sdk-0.1.16}/mo_diag.py +0 -0
  100. {matrixone_python_sdk-0.1.14 → matrixone_python_sdk-0.1.16}/requirements.txt +0 -0
  101. {matrixone_python_sdk-0.1.14 → matrixone_python_sdk-0.1.16}/setup.cfg +0 -0
  102. {matrixone_python_sdk-0.1.14 → 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.14
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
@@ -23,11 +23,13 @@ except ImportError:
23
23
 
24
24
  try:
25
25
  from sqlalchemy import text
26
+ from sqlalchemy.engine import URL
26
27
  from sqlalchemy.ext.asyncio import AsyncEngine, create_async_engine
27
28
  except ImportError:
28
29
  create_async_engine = None
29
30
  AsyncEngine = None
30
31
  text = None
32
+ URL = None
31
33
 
32
34
  from contextlib import asynccontextmanager
33
35
  from datetime import datetime
@@ -862,19 +864,21 @@ class AsyncClient(BaseMatrixOneClient):
862
864
  if not create_async_engine:
863
865
  raise ConnectionError("SQLAlchemy async engine not available. Please install sqlalchemy[asyncio]")
864
866
 
865
- # Build connection string for async engine
866
- connection_string = (
867
- f"mysql+aiomysql://{self._connection_params['user']}:"
868
- f"{self._connection_params['password']}@"
869
- f"{self._connection_params['host']}:"
870
- f"{self._connection_params['port']}/"
871
- f"{self._connection_params['database'] or ''}"
872
- f"?charset={self._connection_params['charset']}"
867
+ # Build connection URL using SQLAlchemy's URL.create() for proper escaping
868
+ # of special characters in user/password (e.g., @, #, %, etc.)
869
+ url = URL.create(
870
+ drivername="mysql+aiomysql",
871
+ username=self._connection_params["user"],
872
+ password=self._connection_params["password"],
873
+ host=self._connection_params["host"],
874
+ port=self._connection_params["port"],
875
+ database=self._connection_params["database"] or None,
876
+ query={"charset": self._connection_params["charset"]},
873
877
  )
874
878
 
875
879
  # Create async engine with connection pooling
876
880
  engine = create_async_engine(
877
- connection_string,
881
+ url,
878
882
  pool_size=5, # Smaller pool size for testing
879
883
  max_overflow=10, # Smaller max overflow
880
884
  pool_timeout=30, # Default pool timeout
@@ -1015,9 +1019,7 @@ class AsyncClient(BaseMatrixOneClient):
1015
1019
 
1016
1020
  return self
1017
1021
 
1018
- async def _execute_with_logging(
1019
- self, connection, sql: str, context: str = "Async SQL execution", override_sql_log_mode: str = None
1020
- ):
1022
+ async def _execute_with_logging(self, connection, sql: str, context: str = "Async SQL execution", log_mode: str = None):
1021
1023
  """
1022
1024
  Execute SQL asynchronously with proper logging through the client's logger.
1023
1025
 
@@ -1029,7 +1031,7 @@ class AsyncClient(BaseMatrixOneClient):
1029
1031
  connection: SQLAlchemy async connection object
1030
1032
  sql: SQL query string
1031
1033
  context: Context description for error logging (default: "Async SQL execution")
1032
- 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
1033
1035
 
1034
1036
  Returns::
1035
1037
 
@@ -1054,26 +1056,24 @@ class AsyncClient(BaseMatrixOneClient):
1054
1056
  if result.returns_rows:
1055
1057
  # For SELECT queries, we can't consume the result to count rows
1056
1058
  # So we just log without row count
1057
- self.logger.log_query(
1058
- sql, execution_time, None, success=True, override_sql_log_mode=override_sql_log_mode
1059
- )
1059
+ self.logger.log_query(sql, execution_time, None, success=True, log_mode=log_mode)
1060
1060
  else:
1061
1061
  # For DML queries (INSERT/UPDATE/DELETE), we can get rowcount
1062
- self.logger.log_query(
1063
- sql, execution_time, result.rowcount, success=True, override_sql_log_mode=override_sql_log_mode
1064
- )
1062
+ self.logger.log_query(sql, execution_time, result.rowcount, success=True, log_mode=log_mode)
1065
1063
  except Exception:
1066
1064
  # Fallback: just log the query without row count
1067
- 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)
1068
1066
 
1069
1067
  return result
1070
1068
  except Exception as e:
1071
1069
  execution_time = time.time() - start_time
1072
- 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)
1073
1071
  self.logger.log_error(e, context=context)
1074
- raise
1072
+ from .client import _classify_db_error
1075
1073
 
1076
- 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:
1077
1077
  """
1078
1078
  Execute SQL query or SQLAlchemy statement asynchronously without transaction isolation.
1079
1079
 
@@ -1107,7 +1107,7 @@ class AsyncClient(BaseMatrixOneClient):
1107
1107
  substituted for '?' placeholders in order. Automatically escaped to prevent
1108
1108
  SQL injection. Ignored for SQLAlchemy statements.
1109
1109
 
1110
- _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.
1111
1111
  Options: 'off', 'simple', 'full'. If None, uses client's global sql_log_mode
1112
1112
  setting. Useful for debugging or disabling logs for frequently-executed queries.
1113
1113
 
@@ -1293,13 +1293,13 @@ class AsyncClient(BaseMatrixOneClient):
1293
1293
  # Disable logging for frequently executed query
1294
1294
  result = await client.execute(
1295
1295
  select(User).where(User.id == 1),
1296
- _log_mode='off'
1296
+ log_mode='off'
1297
1297
  )
1298
1298
 
1299
1299
  # Force full SQL logging for debugging
1300
1300
  result = await client.execute(
1301
1301
  select(User).where(User.name.like('%test%')),
1302
- _log_mode='full'
1302
+ log_mode='full'
1303
1303
  )
1304
1304
 
1305
1305
  await client.disconnect()
@@ -1321,7 +1321,7 @@ class AsyncClient(BaseMatrixOneClient):
1321
1321
  2. **Use parameters**: Always use parameter binding to prevent SQL injection
1322
1322
  3. **Session for transactions**: Use client.session() for atomic operations
1323
1323
  4. **Use asyncio.gather()**: For concurrent independent queries
1324
- 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
1325
1325
  6. **Handle exceptions**: Wrap execute() in try-except for error handling
1326
1326
 
1327
1327
  See Also:
@@ -1342,7 +1342,8 @@ class AsyncClient(BaseMatrixOneClient):
1342
1342
  if not isinstance(sql_or_stmt, str):
1343
1343
  # SQLAlchemy statement - delegate to session for consistent behavior
1344
1344
  async with self.session() as session:
1345
- 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')
1346
1347
 
1347
1348
  # Convert SQLAlchemy result to AsyncResultSet
1348
1349
  if hasattr(result, 'returns_rows') and result.returns_rows:
@@ -1356,7 +1357,7 @@ class AsyncClient(BaseMatrixOneClient):
1356
1357
  execution_time,
1357
1358
  len(rows),
1358
1359
  success=True,
1359
- override_sql_log_mode=_log_mode,
1360
+ log_mode=log_mode,
1360
1361
  )
1361
1362
  return async_result
1362
1363
  else:
@@ -1368,7 +1369,7 @@ class AsyncClient(BaseMatrixOneClient):
1368
1369
  execution_time,
1369
1370
  result.rowcount,
1370
1371
  success=True,
1371
- override_sql_log_mode=_log_mode,
1372
+ log_mode=log_mode,
1372
1373
  )
1373
1374
  return async_result
1374
1375
 
@@ -1394,15 +1395,11 @@ class AsyncClient(BaseMatrixOneClient):
1394
1395
  rows = result.fetchall()
1395
1396
  columns = list(result.keys()) if hasattr(result, "keys") else []
1396
1397
  async_result = AsyncResultSet(columns, rows)
1397
- self.logger.log_query(
1398
- sql_or_stmt, execution_time, len(rows), success=True, override_sql_log_mode=_log_mode
1399
- )
1398
+ self.logger.log_query(sql_or_stmt, execution_time, len(rows), success=True, log_mode=log_mode)
1400
1399
  return async_result
1401
1400
  else:
1402
1401
  async_result = AsyncResultSet([], [], affected_rows=result.rowcount)
1403
- self.logger.log_query(
1404
- sql_or_stmt, execution_time, result.rowcount, success=True, override_sql_log_mode=_log_mode
1405
- )
1402
+ self.logger.log_query(sql_or_stmt, execution_time, result.rowcount, success=True, log_mode=log_mode)
1406
1403
  return async_result
1407
1404
 
1408
1405
  except Exception as e:
@@ -1420,82 +1417,9 @@ class AsyncClient(BaseMatrixOneClient):
1420
1417
 
1421
1418
  print(f"Warning: Error logging failed: {log_err}", file=sys.stderr)
1422
1419
 
1423
- # Extract user-friendly error message
1424
- error_msg = str(e)
1420
+ from .client import _classify_db_error
1425
1421
 
1426
- # Handle common database errors with helpful messages
1427
- # Check for "does not exist" first before "syntax error"
1428
- if (
1429
- 'does not exist' in error_msg.lower()
1430
- or 'no such table' in error_msg.lower()
1431
- or 'doesn\'t exist' in error_msg.lower()
1432
- ):
1433
- # Table doesn't exist
1434
- import re
1435
-
1436
- match = re.search(r"(?:table|database)\s+[\"']?(\w+)[\"']?\s+does not exist", error_msg, re.IGNORECASE)
1437
- if match:
1438
- obj_name = match.group(1)
1439
- raise QueryError(
1440
- f"Table or database '{obj_name}' does not exist. "
1441
- f"Create it first using client.create_table() or CREATE TABLE/DATABASE statement."
1442
- ) from None
1443
- else:
1444
- raise QueryError(f"Object not found: {error_msg}") from None
1445
-
1446
- elif 'already exists' in error_msg.lower() and '1050' in error_msg:
1447
- # Table already exists
1448
- import re
1449
-
1450
- match = re.search(r"table\s+(\w+)\s+already\s+exists", error_msg, re.IGNORECASE)
1451
- if match:
1452
- table_name = match.group(1)
1453
- raise QueryError(
1454
- f"Table '{table_name}' already exists. "
1455
- f"Use DROP TABLE {table_name} or client.drop_table() to remove it first."
1456
- ) from None
1457
- else:
1458
- raise QueryError(f"Object already exists: {error_msg}") from None
1459
-
1460
- elif 'duplicate' in error_msg.lower() and ('1062' in error_msg or '1061' in error_msg):
1461
- # Duplicate key/entry
1462
- raise QueryError(
1463
- f"Duplicate entry error: {error_msg}. "
1464
- f"Check for duplicate primary key or unique constraint violations."
1465
- ) from None
1466
-
1467
- elif 'syntax error' in error_msg.lower() or '1064' in error_msg:
1468
- # SQL syntax error
1469
- sql_preview = final_sql[:200] + '...' if len(final_sql) > 200 else final_sql
1470
- raise QueryError(f"SQL syntax error: {error_msg}\n" f"Query: {sql_preview}") from None
1471
-
1472
- elif 'column' in error_msg.lower() and ('unknown' in error_msg.lower() or 'not found' in error_msg.lower()):
1473
- # Column doesn't exist
1474
- raise QueryError(f"Column not found: {error_msg}. " f"Check your column names and table schema.") from None
1475
-
1476
- elif 'cannot be null' in error_msg.lower() or '1048' in error_msg:
1477
- # NULL constraint violation
1478
- raise QueryError(
1479
- f"NULL constraint violation: {error_msg}. " f"Some columns require non-NULL values."
1480
- ) from None
1481
-
1482
- elif 'not supported' in error_msg.lower() and '20105' in error_msg:
1483
- # MatrixOne-specific: feature not supported
1484
- raise QueryError(
1485
- f"MatrixOne feature limitation: {error_msg}. "
1486
- f"This feature may require additional configuration or is not yet supported."
1487
- ) from None
1488
-
1489
- elif 'bind parameter' in error_msg.lower() or 'InvalidRequestError' in error_msg:
1490
- # SQLAlchemy bind parameter error
1491
- raise QueryError(
1492
- f"Parameter binding error: {error_msg}. "
1493
- f"This might be caused by special characters in your data (colons in JSON, etc.)"
1494
- ) from None
1495
-
1496
- else:
1497
- # Generic error - cleaner message without full SQLAlchemy stack
1498
- raise QueryError(f"Query execution failed: {error_msg}") from None
1422
+ raise _classify_db_error(e, sql_or_stmt) from None
1499
1423
 
1500
1424
  def _substitute_parameters(self, sql: str, params: Optional[Tuple] = None) -> str:
1501
1425
  """