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.
- {matrixone_python_sdk-0.1.14/matrixone_python_sdk.egg-info → matrixone_python_sdk-0.1.16}/PKG-INFO +1 -1
- {matrixone_python_sdk-0.1.14 → matrixone_python_sdk-0.1.16}/README.md +13 -0
- matrixone_python_sdk-0.1.16/examples/example_branch_builder.py +226 -0
- matrixone_python_sdk-0.1.16/examples/example_clone_builder.py +54 -0
- {matrixone_python_sdk-0.1.14 → matrixone_python_sdk-0.1.16}/matrixone/__init__.py +24 -0
- matrixone_python_sdk-0.1.16/matrixone/_utils.py +37 -0
- {matrixone_python_sdk-0.1.14 → matrixone_python_sdk-0.1.16}/matrixone/async_client.py +35 -111
- {matrixone_python_sdk-0.1.14 → matrixone_python_sdk-0.1.16}/matrixone/branch.py +6 -36
- matrixone_python_sdk-0.1.16/matrixone/branch_builder.py +312 -0
- {matrixone_python_sdk-0.1.14 → matrixone_python_sdk-0.1.16}/matrixone/client.py +222 -128
- {matrixone_python_sdk-0.1.14 → matrixone_python_sdk-0.1.16}/matrixone/clone.py +4 -2
- matrixone_python_sdk-0.1.16/matrixone/clone_builder.py +138 -0
- {matrixone_python_sdk-0.1.14 → matrixone_python_sdk-0.1.16}/matrixone/fulltext_manager.py +15 -18
- {matrixone_python_sdk-0.1.14 → matrixone_python_sdk-0.1.16}/matrixone/logger.py +63 -389
- {matrixone_python_sdk-0.1.14 → matrixone_python_sdk-0.1.16}/matrixone/session.py +22 -24
- {matrixone_python_sdk-0.1.14 → matrixone_python_sdk-0.1.16}/matrixone/sqlalchemy_ext/dialect.py +26 -1
- {matrixone_python_sdk-0.1.14 → matrixone_python_sdk-0.1.16}/matrixone/vector_manager.py +48 -53
- {matrixone_python_sdk-0.1.14 → matrixone_python_sdk-0.1.16/matrixone_python_sdk.egg-info}/PKG-INFO +1 -1
- {matrixone_python_sdk-0.1.14 → matrixone_python_sdk-0.1.16}/matrixone_python_sdk.egg-info/SOURCES.txt +5 -0
- {matrixone_python_sdk-0.1.14 → matrixone_python_sdk-0.1.16}/pyproject.toml +1 -1
- {matrixone_python_sdk-0.1.14 → matrixone_python_sdk-0.1.16}/LICENSE +0 -0
- {matrixone_python_sdk-0.1.14 → matrixone_python_sdk-0.1.16}/MANIFEST.in +0 -0
- {matrixone_python_sdk-0.1.14 → matrixone_python_sdk-0.1.16}/README_USER.md +0 -0
- {matrixone_python_sdk-0.1.14 → matrixone_python_sdk-0.1.16}/examples/example_01_basic_connection.py +0 -0
- {matrixone_python_sdk-0.1.14 → matrixone_python_sdk-0.1.16}/examples/example_02_account_management.py +0 -0
- {matrixone_python_sdk-0.1.14 → matrixone_python_sdk-0.1.16}/examples/example_03_async_operations.py +0 -0
- {matrixone_python_sdk-0.1.14 → matrixone_python_sdk-0.1.16}/examples/example_04_transaction_management.py +0 -0
- {matrixone_python_sdk-0.1.14 → matrixone_python_sdk-0.1.16}/examples/example_05_snapshot_restore.py +0 -0
- {matrixone_python_sdk-0.1.14 → matrixone_python_sdk-0.1.16}/examples/example_06_sqlalchemy_integration.py +0 -0
- {matrixone_python_sdk-0.1.14 → matrixone_python_sdk-0.1.16}/examples/example_07_advanced_features.py +0 -0
- {matrixone_python_sdk-0.1.14 → matrixone_python_sdk-0.1.16}/examples/example_08_pubsub_operations.py +0 -0
- {matrixone_python_sdk-0.1.14 → matrixone_python_sdk-0.1.16}/examples/example_09_logger_integration.py +0 -0
- {matrixone_python_sdk-0.1.14 → matrixone_python_sdk-0.1.16}/examples/example_10_version_management.py +0 -0
- {matrixone_python_sdk-0.1.14 → matrixone_python_sdk-0.1.16}/examples/example_11_matrixone_version_demo.py +0 -0
- {matrixone_python_sdk-0.1.14 → matrixone_python_sdk-0.1.16}/examples/example_12_vector_basics.py +0 -0
- {matrixone_python_sdk-0.1.14 → matrixone_python_sdk-0.1.16}/examples/example_13_vector_indexes.py +0 -0
- {matrixone_python_sdk-0.1.14 → matrixone_python_sdk-0.1.16}/examples/example_14_vector_search.py +0 -0
- {matrixone_python_sdk-0.1.14 → matrixone_python_sdk-0.1.16}/examples/example_15_vector_advanced.py +0 -0
- {matrixone_python_sdk-0.1.14 → matrixone_python_sdk-0.1.16}/examples/example_18_snapshot_orm.py +0 -0
- {matrixone_python_sdk-0.1.14 → matrixone_python_sdk-0.1.16}/examples/example_19_sqlalchemy_style_orm.py +0 -0
- {matrixone_python_sdk-0.1.14 → matrixone_python_sdk-0.1.16}/examples/example_20_sqlalchemy_engine_integration.py +0 -0
- {matrixone_python_sdk-0.1.14 → matrixone_python_sdk-0.1.16}/examples/example_21_advanced_orm_features.py +0 -0
- {matrixone_python_sdk-0.1.14 → matrixone_python_sdk-0.1.16}/examples/example_22_unified_sql_builder.py +0 -0
- {matrixone_python_sdk-0.1.14 → matrixone_python_sdk-0.1.16}/examples/example_23_load_data_operations.py +0 -0
- {matrixone_python_sdk-0.1.14 → matrixone_python_sdk-0.1.16}/examples/example_24_query_update.py +0 -0
- {matrixone_python_sdk-0.1.14 → matrixone_python_sdk-0.1.16}/examples/example_25_metadata_operations.py +0 -0
- {matrixone_python_sdk-0.1.14 → matrixone_python_sdk-0.1.16}/examples/example_26_stage_operations.py +0 -0
- {matrixone_python_sdk-0.1.14 → matrixone_python_sdk-0.1.16}/examples/example_27_export_operations.py +0 -0
- {matrixone_python_sdk-0.1.14 → matrixone_python_sdk-0.1.16}/examples/example_28_sqlalchemy_select.py +0 -0
- {matrixone_python_sdk-0.1.14 → matrixone_python_sdk-0.1.16}/examples/example_29_complex_queries.py +0 -0
- {matrixone_python_sdk-0.1.14 → matrixone_python_sdk-0.1.16}/examples/example_30_with_snapshot_method.py +0 -0
- {matrixone_python_sdk-0.1.14 → matrixone_python_sdk-0.1.16}/examples/example_31_cdc_operations.py +0 -0
- {matrixone_python_sdk-0.1.14 → matrixone_python_sdk-0.1.16}/examples/example_32_branch_operations.py +0 -0
- {matrixone_python_sdk-0.1.14 → matrixone_python_sdk-0.1.16}/examples/example_33_ivf_rank_search.py +0 -0
- {matrixone_python_sdk-0.1.14 → matrixone_python_sdk-0.1.16}/examples/example_34_branch_diff_count.py +0 -0
- {matrixone_python_sdk-0.1.14 → matrixone_python_sdk-0.1.16}/examples/example_connection_hooks.py +0 -0
- {matrixone_python_sdk-0.1.14 → matrixone_python_sdk-0.1.16}/examples/example_dynamic_logging.py +0 -0
- {matrixone_python_sdk-0.1.14 → matrixone_python_sdk-0.1.16}/examples/example_ivf_stats_complete.py +0 -0
- {matrixone_python_sdk-0.1.14 → matrixone_python_sdk-0.1.16}/matrixone/account.py +0 -0
- {matrixone_python_sdk-0.1.14 → matrixone_python_sdk-0.1.16}/matrixone/async_orm.py +0 -0
- {matrixone_python_sdk-0.1.14 → matrixone_python_sdk-0.1.16}/matrixone/base_client.py +0 -0
- {matrixone_python_sdk-0.1.14 → matrixone_python_sdk-0.1.16}/matrixone/cdc.py +0 -0
- {matrixone_python_sdk-0.1.14 → matrixone_python_sdk-0.1.16}/matrixone/cli_tools.py +0 -0
- {matrixone_python_sdk-0.1.14 → matrixone_python_sdk-0.1.16}/matrixone/config.py +0 -0
- {matrixone_python_sdk-0.1.14 → matrixone_python_sdk-0.1.16}/matrixone/connection_hooks.py +0 -0
- {matrixone_python_sdk-0.1.14 → matrixone_python_sdk-0.1.16}/matrixone/exceptions.py +0 -0
- {matrixone_python_sdk-0.1.14 → matrixone_python_sdk-0.1.16}/matrixone/export.py +0 -0
- {matrixone_python_sdk-0.1.14 → matrixone_python_sdk-0.1.16}/matrixone/index_utils.py +0 -0
- {matrixone_python_sdk-0.1.14 → matrixone_python_sdk-0.1.16}/matrixone/ivf_rank.py +0 -0
- {matrixone_python_sdk-0.1.14 → matrixone_python_sdk-0.1.16}/matrixone/load_data.py +0 -0
- {matrixone_python_sdk-0.1.14 → matrixone_python_sdk-0.1.16}/matrixone/metadata.py +0 -0
- {matrixone_python_sdk-0.1.14 → matrixone_python_sdk-0.1.16}/matrixone/moctl.py +0 -0
- {matrixone_python_sdk-0.1.14 → matrixone_python_sdk-0.1.16}/matrixone/orm.py +0 -0
- {matrixone_python_sdk-0.1.14 → matrixone_python_sdk-0.1.16}/matrixone/pitr.py +0 -0
- {matrixone_python_sdk-0.1.14 → matrixone_python_sdk-0.1.16}/matrixone/pubsub.py +0 -0
- {matrixone_python_sdk-0.1.14 → matrixone_python_sdk-0.1.16}/matrixone/restore.py +0 -0
- {matrixone_python_sdk-0.1.14 → matrixone_python_sdk-0.1.16}/matrixone/search_vector_index.py +0 -0
- {matrixone_python_sdk-0.1.14 → matrixone_python_sdk-0.1.16}/matrixone/snapshot.py +0 -0
- {matrixone_python_sdk-0.1.14 → matrixone_python_sdk-0.1.16}/matrixone/sql_builder.py +0 -0
- {matrixone_python_sdk-0.1.14 → matrixone_python_sdk-0.1.16}/matrixone/sqlalchemy_ext/__init__.py +0 -0
- {matrixone_python_sdk-0.1.14 → matrixone_python_sdk-0.1.16}/matrixone/sqlalchemy_ext/fulltext_index.py +0 -0
- {matrixone_python_sdk-0.1.14 → matrixone_python_sdk-0.1.16}/matrixone/sqlalchemy_ext/fulltext_search.py +0 -0
- {matrixone_python_sdk-0.1.14 → matrixone_python_sdk-0.1.16}/matrixone/sqlalchemy_ext/hnsw_config.py +0 -0
- {matrixone_python_sdk-0.1.14 → matrixone_python_sdk-0.1.16}/matrixone/sqlalchemy_ext/ivf_config.py +0 -0
- {matrixone_python_sdk-0.1.14 → matrixone_python_sdk-0.1.16}/matrixone/sqlalchemy_ext/json_functions.py +0 -0
- {matrixone_python_sdk-0.1.14 → matrixone_python_sdk-0.1.16}/matrixone/sqlalchemy_ext/json_type.py +0 -0
- {matrixone_python_sdk-0.1.14 → matrixone_python_sdk-0.1.16}/matrixone/sqlalchemy_ext/snapshot.py +0 -0
- {matrixone_python_sdk-0.1.14 → matrixone_python_sdk-0.1.16}/matrixone/sqlalchemy_ext/table_builder.py +0 -0
- {matrixone_python_sdk-0.1.14 → matrixone_python_sdk-0.1.16}/matrixone/sqlalchemy_ext/vector_index.py +0 -0
- {matrixone_python_sdk-0.1.14 → matrixone_python_sdk-0.1.16}/matrixone/sqlalchemy_ext/vector_type.py +0 -0
- {matrixone_python_sdk-0.1.14 → matrixone_python_sdk-0.1.16}/matrixone/sqlalchemy_select.py +0 -0
- {matrixone_python_sdk-0.1.14 → matrixone_python_sdk-0.1.16}/matrixone/stage.py +0 -0
- {matrixone_python_sdk-0.1.14 → matrixone_python_sdk-0.1.16}/matrixone/version.py +0 -0
- {matrixone_python_sdk-0.1.14 → matrixone_python_sdk-0.1.16}/matrixone_python_sdk.egg-info/dependency_links.txt +0 -0
- {matrixone_python_sdk-0.1.14 → matrixone_python_sdk-0.1.16}/matrixone_python_sdk.egg-info/entry_points.txt +0 -0
- {matrixone_python_sdk-0.1.14 → matrixone_python_sdk-0.1.16}/matrixone_python_sdk.egg-info/not-zip-safe +0 -0
- {matrixone_python_sdk-0.1.14 → matrixone_python_sdk-0.1.16}/matrixone_python_sdk.egg-info/requires.txt +0 -0
- {matrixone_python_sdk-0.1.14 → matrixone_python_sdk-0.1.16}/matrixone_python_sdk.egg-info/top_level.txt +0 -0
- {matrixone_python_sdk-0.1.14 → matrixone_python_sdk-0.1.16}/mo_diag.py +0 -0
- {matrixone_python_sdk-0.1.14 → matrixone_python_sdk-0.1.16}/requirements.txt +0 -0
- {matrixone_python_sdk-0.1.14 → matrixone_python_sdk-0.1.16}/setup.cfg +0 -0
- {matrixone_python_sdk-0.1.14 → matrixone_python_sdk-0.1.16}/setup.py +0 -0
{matrixone_python_sdk-0.1.14/matrixone_python_sdk.egg-info → matrixone_python_sdk-0.1.16}/PKG-INFO
RENAMED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: matrixone-python-sdk
|
|
3
|
-
Version: 0.1.
|
|
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
|
|
866
|
-
|
|
867
|
-
|
|
868
|
-
|
|
869
|
-
|
|
870
|
-
|
|
871
|
-
|
|
872
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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,
|
|
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,
|
|
1070
|
+
self.logger.log_query(sql, execution_time, success=False, log_mode=log_mode)
|
|
1073
1071
|
self.logger.log_error(e, context=context)
|
|
1074
|
-
|
|
1072
|
+
from .client import _classify_db_error
|
|
1075
1073
|
|
|
1076
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
1424
|
-
error_msg = str(e)
|
|
1420
|
+
from .client import _classify_db_error
|
|
1425
1421
|
|
|
1426
|
-
|
|
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
|
"""
|