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.
- {matrixone_python_sdk-0.1.15/matrixone_python_sdk.egg-info → matrixone_python_sdk-0.1.16}/PKG-INFO +1 -1
- {matrixone_python_sdk-0.1.15 → 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.15 → 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.15 → matrixone_python_sdk-0.1.16}/matrixone/async_client.py +22 -102
- {matrixone_python_sdk-0.1.15 → 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.15 → matrixone_python_sdk-0.1.16}/matrixone/client.py +202 -115
- {matrixone_python_sdk-0.1.15 → 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.15 → matrixone_python_sdk-0.1.16}/matrixone/fulltext_manager.py +15 -18
- {matrixone_python_sdk-0.1.15 → matrixone_python_sdk-0.1.16}/matrixone/logger.py +63 -389
- {matrixone_python_sdk-0.1.15 → matrixone_python_sdk-0.1.16}/matrixone/session.py +22 -24
- {matrixone_python_sdk-0.1.15 → matrixone_python_sdk-0.1.16}/matrixone/vector_manager.py +48 -53
- {matrixone_python_sdk-0.1.15 → matrixone_python_sdk-0.1.16/matrixone_python_sdk.egg-info}/PKG-INFO +1 -1
- {matrixone_python_sdk-0.1.15 → matrixone_python_sdk-0.1.16}/matrixone_python_sdk.egg-info/SOURCES.txt +5 -0
- {matrixone_python_sdk-0.1.15 → matrixone_python_sdk-0.1.16}/pyproject.toml +1 -1
- {matrixone_python_sdk-0.1.15 → matrixone_python_sdk-0.1.16}/LICENSE +0 -0
- {matrixone_python_sdk-0.1.15 → matrixone_python_sdk-0.1.16}/MANIFEST.in +0 -0
- {matrixone_python_sdk-0.1.15 → matrixone_python_sdk-0.1.16}/README_USER.md +0 -0
- {matrixone_python_sdk-0.1.15 → matrixone_python_sdk-0.1.16}/examples/example_01_basic_connection.py +0 -0
- {matrixone_python_sdk-0.1.15 → matrixone_python_sdk-0.1.16}/examples/example_02_account_management.py +0 -0
- {matrixone_python_sdk-0.1.15 → matrixone_python_sdk-0.1.16}/examples/example_03_async_operations.py +0 -0
- {matrixone_python_sdk-0.1.15 → matrixone_python_sdk-0.1.16}/examples/example_04_transaction_management.py +0 -0
- {matrixone_python_sdk-0.1.15 → matrixone_python_sdk-0.1.16}/examples/example_05_snapshot_restore.py +0 -0
- {matrixone_python_sdk-0.1.15 → matrixone_python_sdk-0.1.16}/examples/example_06_sqlalchemy_integration.py +0 -0
- {matrixone_python_sdk-0.1.15 → matrixone_python_sdk-0.1.16}/examples/example_07_advanced_features.py +0 -0
- {matrixone_python_sdk-0.1.15 → matrixone_python_sdk-0.1.16}/examples/example_08_pubsub_operations.py +0 -0
- {matrixone_python_sdk-0.1.15 → matrixone_python_sdk-0.1.16}/examples/example_09_logger_integration.py +0 -0
- {matrixone_python_sdk-0.1.15 → matrixone_python_sdk-0.1.16}/examples/example_10_version_management.py +0 -0
- {matrixone_python_sdk-0.1.15 → matrixone_python_sdk-0.1.16}/examples/example_11_matrixone_version_demo.py +0 -0
- {matrixone_python_sdk-0.1.15 → matrixone_python_sdk-0.1.16}/examples/example_12_vector_basics.py +0 -0
- {matrixone_python_sdk-0.1.15 → matrixone_python_sdk-0.1.16}/examples/example_13_vector_indexes.py +0 -0
- {matrixone_python_sdk-0.1.15 → matrixone_python_sdk-0.1.16}/examples/example_14_vector_search.py +0 -0
- {matrixone_python_sdk-0.1.15 → matrixone_python_sdk-0.1.16}/examples/example_15_vector_advanced.py +0 -0
- {matrixone_python_sdk-0.1.15 → matrixone_python_sdk-0.1.16}/examples/example_18_snapshot_orm.py +0 -0
- {matrixone_python_sdk-0.1.15 → matrixone_python_sdk-0.1.16}/examples/example_19_sqlalchemy_style_orm.py +0 -0
- {matrixone_python_sdk-0.1.15 → matrixone_python_sdk-0.1.16}/examples/example_20_sqlalchemy_engine_integration.py +0 -0
- {matrixone_python_sdk-0.1.15 → matrixone_python_sdk-0.1.16}/examples/example_21_advanced_orm_features.py +0 -0
- {matrixone_python_sdk-0.1.15 → matrixone_python_sdk-0.1.16}/examples/example_22_unified_sql_builder.py +0 -0
- {matrixone_python_sdk-0.1.15 → matrixone_python_sdk-0.1.16}/examples/example_23_load_data_operations.py +0 -0
- {matrixone_python_sdk-0.1.15 → matrixone_python_sdk-0.1.16}/examples/example_24_query_update.py +0 -0
- {matrixone_python_sdk-0.1.15 → matrixone_python_sdk-0.1.16}/examples/example_25_metadata_operations.py +0 -0
- {matrixone_python_sdk-0.1.15 → matrixone_python_sdk-0.1.16}/examples/example_26_stage_operations.py +0 -0
- {matrixone_python_sdk-0.1.15 → matrixone_python_sdk-0.1.16}/examples/example_27_export_operations.py +0 -0
- {matrixone_python_sdk-0.1.15 → matrixone_python_sdk-0.1.16}/examples/example_28_sqlalchemy_select.py +0 -0
- {matrixone_python_sdk-0.1.15 → matrixone_python_sdk-0.1.16}/examples/example_29_complex_queries.py +0 -0
- {matrixone_python_sdk-0.1.15 → matrixone_python_sdk-0.1.16}/examples/example_30_with_snapshot_method.py +0 -0
- {matrixone_python_sdk-0.1.15 → matrixone_python_sdk-0.1.16}/examples/example_31_cdc_operations.py +0 -0
- {matrixone_python_sdk-0.1.15 → matrixone_python_sdk-0.1.16}/examples/example_32_branch_operations.py +0 -0
- {matrixone_python_sdk-0.1.15 → matrixone_python_sdk-0.1.16}/examples/example_33_ivf_rank_search.py +0 -0
- {matrixone_python_sdk-0.1.15 → matrixone_python_sdk-0.1.16}/examples/example_34_branch_diff_count.py +0 -0
- {matrixone_python_sdk-0.1.15 → matrixone_python_sdk-0.1.16}/examples/example_connection_hooks.py +0 -0
- {matrixone_python_sdk-0.1.15 → matrixone_python_sdk-0.1.16}/examples/example_dynamic_logging.py +0 -0
- {matrixone_python_sdk-0.1.15 → matrixone_python_sdk-0.1.16}/examples/example_ivf_stats_complete.py +0 -0
- {matrixone_python_sdk-0.1.15 → matrixone_python_sdk-0.1.16}/matrixone/account.py +0 -0
- {matrixone_python_sdk-0.1.15 → matrixone_python_sdk-0.1.16}/matrixone/async_orm.py +0 -0
- {matrixone_python_sdk-0.1.15 → matrixone_python_sdk-0.1.16}/matrixone/base_client.py +0 -0
- {matrixone_python_sdk-0.1.15 → matrixone_python_sdk-0.1.16}/matrixone/cdc.py +0 -0
- {matrixone_python_sdk-0.1.15 → matrixone_python_sdk-0.1.16}/matrixone/cli_tools.py +0 -0
- {matrixone_python_sdk-0.1.15 → matrixone_python_sdk-0.1.16}/matrixone/config.py +0 -0
- {matrixone_python_sdk-0.1.15 → matrixone_python_sdk-0.1.16}/matrixone/connection_hooks.py +0 -0
- {matrixone_python_sdk-0.1.15 → matrixone_python_sdk-0.1.16}/matrixone/exceptions.py +0 -0
- {matrixone_python_sdk-0.1.15 → matrixone_python_sdk-0.1.16}/matrixone/export.py +0 -0
- {matrixone_python_sdk-0.1.15 → matrixone_python_sdk-0.1.16}/matrixone/index_utils.py +0 -0
- {matrixone_python_sdk-0.1.15 → matrixone_python_sdk-0.1.16}/matrixone/ivf_rank.py +0 -0
- {matrixone_python_sdk-0.1.15 → matrixone_python_sdk-0.1.16}/matrixone/load_data.py +0 -0
- {matrixone_python_sdk-0.1.15 → matrixone_python_sdk-0.1.16}/matrixone/metadata.py +0 -0
- {matrixone_python_sdk-0.1.15 → matrixone_python_sdk-0.1.16}/matrixone/moctl.py +0 -0
- {matrixone_python_sdk-0.1.15 → matrixone_python_sdk-0.1.16}/matrixone/orm.py +0 -0
- {matrixone_python_sdk-0.1.15 → matrixone_python_sdk-0.1.16}/matrixone/pitr.py +0 -0
- {matrixone_python_sdk-0.1.15 → matrixone_python_sdk-0.1.16}/matrixone/pubsub.py +0 -0
- {matrixone_python_sdk-0.1.15 → matrixone_python_sdk-0.1.16}/matrixone/restore.py +0 -0
- {matrixone_python_sdk-0.1.15 → matrixone_python_sdk-0.1.16}/matrixone/search_vector_index.py +0 -0
- {matrixone_python_sdk-0.1.15 → matrixone_python_sdk-0.1.16}/matrixone/snapshot.py +0 -0
- {matrixone_python_sdk-0.1.15 → matrixone_python_sdk-0.1.16}/matrixone/sql_builder.py +0 -0
- {matrixone_python_sdk-0.1.15 → matrixone_python_sdk-0.1.16}/matrixone/sqlalchemy_ext/__init__.py +0 -0
- {matrixone_python_sdk-0.1.15 → matrixone_python_sdk-0.1.16}/matrixone/sqlalchemy_ext/dialect.py +0 -0
- {matrixone_python_sdk-0.1.15 → matrixone_python_sdk-0.1.16}/matrixone/sqlalchemy_ext/fulltext_index.py +0 -0
- {matrixone_python_sdk-0.1.15 → matrixone_python_sdk-0.1.16}/matrixone/sqlalchemy_ext/fulltext_search.py +0 -0
- {matrixone_python_sdk-0.1.15 → matrixone_python_sdk-0.1.16}/matrixone/sqlalchemy_ext/hnsw_config.py +0 -0
- {matrixone_python_sdk-0.1.15 → matrixone_python_sdk-0.1.16}/matrixone/sqlalchemy_ext/ivf_config.py +0 -0
- {matrixone_python_sdk-0.1.15 → matrixone_python_sdk-0.1.16}/matrixone/sqlalchemy_ext/json_functions.py +0 -0
- {matrixone_python_sdk-0.1.15 → matrixone_python_sdk-0.1.16}/matrixone/sqlalchemy_ext/json_type.py +0 -0
- {matrixone_python_sdk-0.1.15 → matrixone_python_sdk-0.1.16}/matrixone/sqlalchemy_ext/snapshot.py +0 -0
- {matrixone_python_sdk-0.1.15 → matrixone_python_sdk-0.1.16}/matrixone/sqlalchemy_ext/table_builder.py +0 -0
- {matrixone_python_sdk-0.1.15 → matrixone_python_sdk-0.1.16}/matrixone/sqlalchemy_ext/vector_index.py +0 -0
- {matrixone_python_sdk-0.1.15 → matrixone_python_sdk-0.1.16}/matrixone/sqlalchemy_ext/vector_type.py +0 -0
- {matrixone_python_sdk-0.1.15 → matrixone_python_sdk-0.1.16}/matrixone/sqlalchemy_select.py +0 -0
- {matrixone_python_sdk-0.1.15 → matrixone_python_sdk-0.1.16}/matrixone/stage.py +0 -0
- {matrixone_python_sdk-0.1.15 → matrixone_python_sdk-0.1.16}/matrixone/version.py +0 -0
- {matrixone_python_sdk-0.1.15 → matrixone_python_sdk-0.1.16}/matrixone_python_sdk.egg-info/dependency_links.txt +0 -0
- {matrixone_python_sdk-0.1.15 → matrixone_python_sdk-0.1.16}/matrixone_python_sdk.egg-info/entry_points.txt +0 -0
- {matrixone_python_sdk-0.1.15 → matrixone_python_sdk-0.1.16}/matrixone_python_sdk.egg-info/not-zip-safe +0 -0
- {matrixone_python_sdk-0.1.15 → matrixone_python_sdk-0.1.16}/matrixone_python_sdk.egg-info/requires.txt +0 -0
- {matrixone_python_sdk-0.1.15 → matrixone_python_sdk-0.1.16}/matrixone_python_sdk.egg-info/top_level.txt +0 -0
- {matrixone_python_sdk-0.1.15 → matrixone_python_sdk-0.1.16}/mo_diag.py +0 -0
- {matrixone_python_sdk-0.1.15 → matrixone_python_sdk-0.1.16}/requirements.txt +0 -0
- {matrixone_python_sdk-0.1.15 → matrixone_python_sdk-0.1.16}/setup.cfg +0 -0
- {matrixone_python_sdk-0.1.15 → matrixone_python_sdk-0.1.16}/setup.py +0 -0
{matrixone_python_sdk-0.1.15/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
|
|
@@ -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
|
-
|
|
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,
|
|
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,
|
|
1070
|
+
self.logger.log_query(sql, execution_time, success=False, log_mode=log_mode)
|
|
1077
1071
|
self.logger.log_error(e, context=context)
|
|
1078
|
-
|
|
1072
|
+
from .client import _classify_db_error
|
|
1079
1073
|
|
|
1080
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
1428
|
-
error_msg = str(e)
|
|
1420
|
+
from .client import _classify_db_error
|
|
1429
1421
|
|
|
1430
|
-
|
|
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
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
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,
|