matrixone-python-sdk 0.1.0__py3-none-any.whl
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/__init__.py +155 -0
- matrixone/account.py +723 -0
- matrixone/async_client.py +3913 -0
- matrixone/async_metadata_manager.py +311 -0
- matrixone/async_orm.py +123 -0
- matrixone/async_vector_index_manager.py +633 -0
- matrixone/base_client.py +208 -0
- matrixone/client.py +4672 -0
- matrixone/config.py +452 -0
- matrixone/connection_hooks.py +286 -0
- matrixone/exceptions.py +89 -0
- matrixone/logger.py +782 -0
- matrixone/metadata.py +820 -0
- matrixone/moctl.py +219 -0
- matrixone/orm.py +2277 -0
- matrixone/pitr.py +646 -0
- matrixone/pubsub.py +771 -0
- matrixone/restore.py +411 -0
- matrixone/search_vector_index.py +1176 -0
- matrixone/snapshot.py +550 -0
- matrixone/sql_builder.py +844 -0
- matrixone/sqlalchemy_ext/__init__.py +161 -0
- matrixone/sqlalchemy_ext/adapters.py +163 -0
- matrixone/sqlalchemy_ext/dialect.py +534 -0
- matrixone/sqlalchemy_ext/fulltext_index.py +895 -0
- matrixone/sqlalchemy_ext/fulltext_search.py +1686 -0
- matrixone/sqlalchemy_ext/hnsw_config.py +194 -0
- matrixone/sqlalchemy_ext/ivf_config.py +252 -0
- matrixone/sqlalchemy_ext/table_builder.py +351 -0
- matrixone/sqlalchemy_ext/vector_index.py +1721 -0
- matrixone/sqlalchemy_ext/vector_type.py +948 -0
- matrixone/version.py +580 -0
- matrixone_python_sdk-0.1.0.dist-info/METADATA +706 -0
- matrixone_python_sdk-0.1.0.dist-info/RECORD +122 -0
- matrixone_python_sdk-0.1.0.dist-info/WHEEL +5 -0
- matrixone_python_sdk-0.1.0.dist-info/entry_points.txt +5 -0
- matrixone_python_sdk-0.1.0.dist-info/licenses/LICENSE +200 -0
- matrixone_python_sdk-0.1.0.dist-info/top_level.txt +2 -0
- tests/__init__.py +19 -0
- tests/offline/__init__.py +20 -0
- tests/offline/conftest.py +77 -0
- tests/offline/test_account.py +703 -0
- tests/offline/test_async_client_query_comprehensive.py +1218 -0
- tests/offline/test_basic.py +54 -0
- tests/offline/test_case_sensitivity.py +227 -0
- tests/offline/test_connection_hooks_offline.py +287 -0
- tests/offline/test_dialect_schema_handling.py +609 -0
- tests/offline/test_explain_methods.py +346 -0
- tests/offline/test_filter_logical_in.py +237 -0
- tests/offline/test_fulltext_search_comprehensive.py +795 -0
- tests/offline/test_ivf_config.py +249 -0
- tests/offline/test_join_methods.py +281 -0
- tests/offline/test_join_sqlalchemy_compatibility.py +276 -0
- tests/offline/test_logical_in_method.py +237 -0
- tests/offline/test_matrixone_version_parsing.py +264 -0
- tests/offline/test_metadata_offline.py +557 -0
- tests/offline/test_moctl.py +300 -0
- tests/offline/test_moctl_simple.py +251 -0
- tests/offline/test_model_support_offline.py +359 -0
- tests/offline/test_model_support_simple.py +225 -0
- tests/offline/test_pinecone_filter_offline.py +377 -0
- tests/offline/test_pitr.py +585 -0
- tests/offline/test_pubsub.py +712 -0
- tests/offline/test_query_update.py +283 -0
- tests/offline/test_restore.py +445 -0
- tests/offline/test_snapshot_comprehensive.py +384 -0
- tests/offline/test_sql_escaping_edge_cases.py +551 -0
- tests/offline/test_sqlalchemy_integration.py +382 -0
- tests/offline/test_sqlalchemy_vector_integration.py +434 -0
- tests/offline/test_table_builder.py +198 -0
- tests/offline/test_unified_filter.py +398 -0
- tests/offline/test_unified_transaction.py +495 -0
- tests/offline/test_vector_index.py +238 -0
- tests/offline/test_vector_operations.py +688 -0
- tests/offline/test_vector_type.py +174 -0
- tests/offline/test_version_core.py +328 -0
- tests/offline/test_version_management.py +372 -0
- tests/offline/test_version_standalone.py +652 -0
- tests/online/__init__.py +20 -0
- tests/online/conftest.py +216 -0
- tests/online/test_account_management.py +194 -0
- tests/online/test_advanced_features.py +344 -0
- tests/online/test_async_client_interfaces.py +330 -0
- tests/online/test_async_client_online.py +285 -0
- tests/online/test_async_model_insert_online.py +293 -0
- tests/online/test_async_orm_online.py +300 -0
- tests/online/test_async_simple_query_online.py +802 -0
- tests/online/test_async_transaction_simple_query.py +300 -0
- tests/online/test_basic_connection.py +130 -0
- tests/online/test_client_online.py +238 -0
- tests/online/test_config.py +90 -0
- tests/online/test_config_validation.py +123 -0
- tests/online/test_connection_hooks_new_online.py +217 -0
- tests/online/test_dialect_schema_handling_online.py +331 -0
- tests/online/test_filter_logical_in_online.py +374 -0
- tests/online/test_fulltext_comprehensive.py +1773 -0
- tests/online/test_fulltext_label_online.py +433 -0
- tests/online/test_fulltext_search_online.py +842 -0
- tests/online/test_ivf_stats_online.py +506 -0
- tests/online/test_logger_integration.py +311 -0
- tests/online/test_matrixone_query_orm.py +540 -0
- tests/online/test_metadata_online.py +579 -0
- tests/online/test_model_insert_online.py +255 -0
- tests/online/test_mysql_driver_validation.py +213 -0
- tests/online/test_orm_advanced_features.py +2022 -0
- tests/online/test_orm_cte_integration.py +269 -0
- tests/online/test_orm_online.py +270 -0
- tests/online/test_pinecone_filter.py +708 -0
- tests/online/test_pubsub_operations.py +352 -0
- tests/online/test_query_methods.py +225 -0
- tests/online/test_query_update_online.py +433 -0
- tests/online/test_search_vector_index.py +557 -0
- tests/online/test_simple_fulltext_online.py +915 -0
- tests/online/test_snapshot_comprehensive.py +998 -0
- tests/online/test_sqlalchemy_engine_integration.py +336 -0
- tests/online/test_sqlalchemy_integration.py +425 -0
- tests/online/test_transaction_contexts.py +1219 -0
- tests/online/test_transaction_insert_methods.py +356 -0
- tests/online/test_transaction_query_methods.py +288 -0
- tests/online/test_unified_filter_online.py +529 -0
- tests/online/test_vector_comprehensive.py +706 -0
- tests/online/test_version_management.py +291 -0
matrixone/snapshot.py
ADDED
@@ -0,0 +1,550 @@
|
|
1
|
+
# Copyright 2021 - 2022 Matrix Origin
|
2
|
+
#
|
3
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
4
|
+
# you may not use this file except in compliance with the License.
|
5
|
+
# You may obtain a copy of the License at
|
6
|
+
#
|
7
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
8
|
+
#
|
9
|
+
# Unless required by applicable law or agreed to in writing, software
|
10
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
11
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
12
|
+
# See the License for the specific language governing permissions and
|
13
|
+
# limitations under the License.
|
14
|
+
|
15
|
+
"""
|
16
|
+
MatrixOne Snapshot Management
|
17
|
+
"""
|
18
|
+
|
19
|
+
from datetime import datetime
|
20
|
+
from enum import Enum
|
21
|
+
from typing import List, Optional, Union
|
22
|
+
|
23
|
+
from .exceptions import CloneError, ConnectionError, SnapshotError
|
24
|
+
from .version import requires_version
|
25
|
+
|
26
|
+
|
27
|
+
class SnapshotLevel(Enum):
|
28
|
+
"""Snapshot level enumeration"""
|
29
|
+
|
30
|
+
CLUSTER = "cluster"
|
31
|
+
ACCOUNT = "account"
|
32
|
+
DATABASE = "database"
|
33
|
+
TABLE = "table"
|
34
|
+
|
35
|
+
|
36
|
+
class Snapshot:
|
37
|
+
"""Snapshot information"""
|
38
|
+
|
39
|
+
def __init__(
|
40
|
+
self,
|
41
|
+
name: str,
|
42
|
+
level: Union[str, SnapshotLevel],
|
43
|
+
created_at: datetime,
|
44
|
+
description: Optional[str] = None,
|
45
|
+
database: Optional[str] = None,
|
46
|
+
table: Optional[str] = None,
|
47
|
+
):
|
48
|
+
self.name = name
|
49
|
+
# Convert string to enum if needed
|
50
|
+
if isinstance(level, str):
|
51
|
+
try:
|
52
|
+
self.level = SnapshotLevel(level.lower())
|
53
|
+
except ValueError:
|
54
|
+
raise SnapshotError(f"Invalid snapshot level: {level}")
|
55
|
+
else:
|
56
|
+
self.level = level
|
57
|
+
self.created_at = created_at
|
58
|
+
self.description = description
|
59
|
+
self.database = database
|
60
|
+
self.table = table
|
61
|
+
|
62
|
+
def __repr__(self):
|
63
|
+
return f"Snapshot(name='{self.name}', level='{self.level}', created_at='{self.created_at}')"
|
64
|
+
|
65
|
+
|
66
|
+
class SnapshotManager:
|
67
|
+
"""
|
68
|
+
Snapshot management for MatrixOne database operations.
|
69
|
+
|
70
|
+
This class provides comprehensive snapshot functionality for creating, managing,
|
71
|
+
and restoring database snapshots at various levels (database, table, or cluster).
|
72
|
+
Snapshots enable point-in-time recovery and data protection capabilities.
|
73
|
+
|
74
|
+
Key Features:
|
75
|
+
|
76
|
+
- Create snapshots at database, table, or cluster level
|
77
|
+
- List and query existing snapshots
|
78
|
+
- Restore data from snapshots
|
79
|
+
- Snapshot lifecycle management
|
80
|
+
- Integration with transaction operations
|
81
|
+
|
82
|
+
Supported Snapshot Levels:
|
83
|
+
- CLUSTER: Full cluster snapshot
|
84
|
+
- DATABASE: Database-level snapshot
|
85
|
+
- TABLE: Table-level snapshot
|
86
|
+
|
87
|
+
Usage Examples::
|
88
|
+
|
89
|
+
# Create a database snapshot
|
90
|
+
snapshot = client.snapshots.create(
|
91
|
+
name='daily_backup',
|
92
|
+
level=SnapshotLevel.DATABASE,
|
93
|
+
database='my_database',
|
94
|
+
description='Daily backup snapshot'
|
95
|
+
)
|
96
|
+
|
97
|
+
# Create a table snapshot
|
98
|
+
snapshot = client.snapshots.create(
|
99
|
+
name='users_backup',
|
100
|
+
level=SnapshotLevel.TABLE,
|
101
|
+
database='my_database',
|
102
|
+
table='users',
|
103
|
+
description='Users table backup'
|
104
|
+
)
|
105
|
+
|
106
|
+
# List all snapshots
|
107
|
+
snapshots = client.snapshots.list()
|
108
|
+
|
109
|
+
# Restore from snapshot
|
110
|
+
client.snapshots.restore('daily_backup', 'restored_database')
|
111
|
+
|
112
|
+
Note: Snapshot functionality requires MatrixOne version 1.0.0 or higher. For older versions,
|
113
|
+
use backup/restore operations instead.
|
114
|
+
"""
|
115
|
+
|
116
|
+
def __init__(self, client):
|
117
|
+
self.client = client
|
118
|
+
|
119
|
+
@requires_version(
|
120
|
+
min_version="1.0.0",
|
121
|
+
feature_name="snapshot_creation",
|
122
|
+
description="Snapshot creation functionality",
|
123
|
+
alternative="Use backup/restore operations instead",
|
124
|
+
)
|
125
|
+
def create(
|
126
|
+
self,
|
127
|
+
name: str,
|
128
|
+
level: Union[str, SnapshotLevel],
|
129
|
+
database: Optional[str] = None,
|
130
|
+
table: Optional[str] = None,
|
131
|
+
description: Optional[str] = None,
|
132
|
+
executor=None,
|
133
|
+
) -> Snapshot:
|
134
|
+
"""
|
135
|
+
Create a snapshot
|
136
|
+
|
137
|
+
Args::
|
138
|
+
|
139
|
+
name: Snapshot name
|
140
|
+
level: Snapshot level (SnapshotLevel enum or string)
|
141
|
+
database: Database name (for database/table level)
|
142
|
+
table: Table name (for table level)
|
143
|
+
description: Snapshot description
|
144
|
+
executor: Optional executor (e.g., transaction wrapper)
|
145
|
+
|
146
|
+
Returns::
|
147
|
+
|
148
|
+
Snapshot object
|
149
|
+
"""
|
150
|
+
if not self.client._engine:
|
151
|
+
raise ConnectionError("Not connected to database")
|
152
|
+
|
153
|
+
# Convert string to enum if needed
|
154
|
+
if isinstance(level, str):
|
155
|
+
try:
|
156
|
+
level_enum = SnapshotLevel(level.lower())
|
157
|
+
except ValueError:
|
158
|
+
raise SnapshotError(f"Invalid snapshot level: {level}")
|
159
|
+
else:
|
160
|
+
level_enum = level
|
161
|
+
|
162
|
+
# Build CREATE SNAPSHOT SQL using correct MatrixOne syntax
|
163
|
+
if level_enum == SnapshotLevel.CLUSTER:
|
164
|
+
sql = f"CREATE SNAPSHOT {name} FOR CLUSTER"
|
165
|
+
elif level_enum == SnapshotLevel.ACCOUNT:
|
166
|
+
sql = f"CREATE SNAPSHOT {name} FOR ACCOUNT"
|
167
|
+
elif level_enum == SnapshotLevel.DATABASE:
|
168
|
+
if not database:
|
169
|
+
raise SnapshotError("Database name required for database level snapshot")
|
170
|
+
sql = f"CREATE SNAPSHOT {name} FOR DATABASE {database}"
|
171
|
+
elif level_enum == SnapshotLevel.TABLE:
|
172
|
+
if not database or not table:
|
173
|
+
raise SnapshotError("Database and table names required for table level snapshot")
|
174
|
+
sql = f"CREATE SNAPSHOT {name} FOR TABLE {database} {table}"
|
175
|
+
|
176
|
+
# Note: MatrixOne doesn't support COMMENT in CREATE SNAPSHOT
|
177
|
+
# if description:
|
178
|
+
# sql += f" COMMENT '{description}'"
|
179
|
+
|
180
|
+
try:
|
181
|
+
# Use provided executor or default client execute
|
182
|
+
execute_func = executor.execute if executor else self.client.execute
|
183
|
+
execute_func(sql)
|
184
|
+
|
185
|
+
# Get snapshot info
|
186
|
+
snapshot_info = self.get(name, executor=executor)
|
187
|
+
return snapshot_info
|
188
|
+
|
189
|
+
except Exception as e:
|
190
|
+
raise SnapshotError(f"Failed to create snapshot: {e}") from None
|
191
|
+
|
192
|
+
def list(self) -> List[Snapshot]:
|
193
|
+
"""
|
194
|
+
List all snapshots
|
195
|
+
|
196
|
+
Returns::
|
197
|
+
|
198
|
+
List of Snapshot objects
|
199
|
+
"""
|
200
|
+
if not self.client._engine:
|
201
|
+
raise ConnectionError("Not connected to database")
|
202
|
+
|
203
|
+
try:
|
204
|
+
# Query snapshot information using mo_catalog.mo_snapshots
|
205
|
+
result = self.client.execute(
|
206
|
+
"""
|
207
|
+
SELECT sname, ts, level, account_name, database_name, table_name
|
208
|
+
FROM mo_catalog.mo_snapshots
|
209
|
+
ORDER BY ts DESC
|
210
|
+
"""
|
211
|
+
)
|
212
|
+
|
213
|
+
snapshots = []
|
214
|
+
for row in result.fetchall():
|
215
|
+
# Convert timestamp to datetime
|
216
|
+
timestamp = datetime.fromtimestamp(row[1] / 1000000000) # Convert nanoseconds to seconds
|
217
|
+
|
218
|
+
# Convert level string to enum
|
219
|
+
level_str = row[2]
|
220
|
+
try:
|
221
|
+
level_enum = SnapshotLevel(level_str.lower())
|
222
|
+
except ValueError:
|
223
|
+
# If enum conversion fails, keep as string for backward compatibility
|
224
|
+
level_enum = level_str
|
225
|
+
|
226
|
+
snapshot = Snapshot(
|
227
|
+
name=row[0], # sname
|
228
|
+
level=level_enum, # level (now as enum)
|
229
|
+
created_at=timestamp, # ts
|
230
|
+
description=None, # Not available
|
231
|
+
database=row[4], # database_name
|
232
|
+
table=row[5], # table_name
|
233
|
+
)
|
234
|
+
snapshots.append(snapshot)
|
235
|
+
|
236
|
+
return snapshots
|
237
|
+
|
238
|
+
except Exception as e:
|
239
|
+
raise SnapshotError(f"Failed to list snapshots: {e}") from None
|
240
|
+
|
241
|
+
def get(self, name: str, executor=None) -> Snapshot:
|
242
|
+
"""
|
243
|
+
Get snapshot by name
|
244
|
+
|
245
|
+
Args::
|
246
|
+
|
247
|
+
name: Snapshot name
|
248
|
+
executor: Optional executor (e.g., transaction wrapper)
|
249
|
+
|
250
|
+
Returns::
|
251
|
+
|
252
|
+
Snapshot object
|
253
|
+
"""
|
254
|
+
if not self.client._engine:
|
255
|
+
raise ConnectionError("Not connected to database")
|
256
|
+
|
257
|
+
try:
|
258
|
+
# Use provided executor or default client execute
|
259
|
+
execute_func = executor.execute if executor else self.client.execute
|
260
|
+
result = execute_func(
|
261
|
+
"""
|
262
|
+
SELECT sname, ts, level, account_name, database_name, table_name
|
263
|
+
FROM mo_catalog.mo_snapshots
|
264
|
+
WHERE sname = :name
|
265
|
+
""",
|
266
|
+
{"name": name},
|
267
|
+
)
|
268
|
+
|
269
|
+
row = result.fetchone()
|
270
|
+
if not row:
|
271
|
+
raise SnapshotError(f"Snapshot '{name}' not found")
|
272
|
+
|
273
|
+
# Convert timestamp to datetime
|
274
|
+
timestamp = datetime.fromtimestamp(row[1] / 1000000000) # Convert nanoseconds to seconds
|
275
|
+
|
276
|
+
# Convert level string to enum
|
277
|
+
level_str = row[2]
|
278
|
+
try:
|
279
|
+
level_enum = SnapshotLevel(level_str.lower())
|
280
|
+
except ValueError:
|
281
|
+
# If enum conversion fails, keep as string for backward compatibility
|
282
|
+
level_enum = level_str
|
283
|
+
|
284
|
+
return Snapshot(
|
285
|
+
name=row[0], # sname
|
286
|
+
level=level_enum, # level (now as enum)
|
287
|
+
created_at=timestamp, # ts
|
288
|
+
description=None, # Not available
|
289
|
+
database=row[4], # database_name
|
290
|
+
table=row[5], # table_name
|
291
|
+
)
|
292
|
+
|
293
|
+
except Exception as e:
|
294
|
+
if "not found" in str(e):
|
295
|
+
raise e
|
296
|
+
raise SnapshotError(f"Failed to get snapshot: {e}")
|
297
|
+
|
298
|
+
def delete(self, name: str, executor=None) -> None:
|
299
|
+
"""
|
300
|
+
Delete snapshot
|
301
|
+
|
302
|
+
Args::
|
303
|
+
|
304
|
+
name: Snapshot name
|
305
|
+
executor: Optional executor (e.g., transaction wrapper)
|
306
|
+
"""
|
307
|
+
if not self.client._engine:
|
308
|
+
raise ConnectionError("Not connected to database")
|
309
|
+
|
310
|
+
try:
|
311
|
+
# Use provided executor or default client execute
|
312
|
+
execute_func = executor.execute if executor else self.client.execute
|
313
|
+
execute_func(f"DROP SNAPSHOT {name}")
|
314
|
+
except Exception as e:
|
315
|
+
raise SnapshotError(f"Failed to delete snapshot: {e}") from None
|
316
|
+
|
317
|
+
def exists(self, name: str) -> bool:
|
318
|
+
"""
|
319
|
+
Check if snapshot exists
|
320
|
+
|
321
|
+
Args::
|
322
|
+
|
323
|
+
name: Snapshot name
|
324
|
+
|
325
|
+
Returns::
|
326
|
+
|
327
|
+
True if snapshot exists, False otherwise
|
328
|
+
"""
|
329
|
+
try:
|
330
|
+
self.get(name)
|
331
|
+
return True
|
332
|
+
except SnapshotError:
|
333
|
+
return False
|
334
|
+
|
335
|
+
|
336
|
+
class CloneManager:
|
337
|
+
"""
|
338
|
+
Clone management for MatrixOne database operations.
|
339
|
+
|
340
|
+
This class provides comprehensive database cloning functionality for creating
|
341
|
+
copies of databases, tables, or data subsets. Cloning enables efficient
|
342
|
+
data replication, testing environments, and data distribution scenarios.
|
343
|
+
|
344
|
+
Key Features:
|
345
|
+
|
346
|
+
- Database cloning with full data replication
|
347
|
+
- Table-level cloning for specific data subsets
|
348
|
+
- Efficient cloning using MatrixOne's native capabilities
|
349
|
+
- Integration with snapshot and restore operations
|
350
|
+
- Transaction-aware cloning operations
|
351
|
+
- Support for both full and incremental cloning
|
352
|
+
|
353
|
+
Supported Cloning Levels:
|
354
|
+
- DATABASE: Full database cloning with all tables and data
|
355
|
+
- TABLE: Table-level cloning with data replication
|
356
|
+
- SUBSET: Partial data cloning based on conditions
|
357
|
+
|
358
|
+
Usage Examples::
|
359
|
+
|
360
|
+
# Initialize clone manager
|
361
|
+
clone = client.clone
|
362
|
+
|
363
|
+
# Clone entire database
|
364
|
+
success = clone.clone_database(
|
365
|
+
target_database='cloned_database',
|
366
|
+
source_database='source_database'
|
367
|
+
)
|
368
|
+
|
369
|
+
# Clone table with data
|
370
|
+
success = clone.clone_table(
|
371
|
+
target_database='cloned_database',
|
372
|
+
target_table='cloned_users',
|
373
|
+
source_database='source_database',
|
374
|
+
source_table='users'
|
375
|
+
)
|
376
|
+
|
377
|
+
# Clone table with conditions
|
378
|
+
success = clone.clone_table_with_conditions(
|
379
|
+
target_database='cloned_database',
|
380
|
+
target_table='active_users',
|
381
|
+
source_database='source_database',
|
382
|
+
source_table='users',
|
383
|
+
conditions='active = 1'
|
384
|
+
)
|
385
|
+
|
386
|
+
# List clone operations
|
387
|
+
clones = clone.list_clones()
|
388
|
+
|
389
|
+
# Get clone status
|
390
|
+
status = clone.get_clone_status('clone_job_id')
|
391
|
+
|
392
|
+
Note: Cloning functionality requires MatrixOne version 1.0.0 or higher. Clone operations may
|
393
|
+
take significant time depending on the amount of data being cloned and database complexity.
|
394
|
+
"""
|
395
|
+
|
396
|
+
def __init__(self, client):
|
397
|
+
self.client = client
|
398
|
+
|
399
|
+
@requires_version(
|
400
|
+
min_version="1.0.0",
|
401
|
+
feature_name="database_cloning",
|
402
|
+
description="Database cloning functionality",
|
403
|
+
alternative="Use CREATE DATABASE and data migration instead",
|
404
|
+
)
|
405
|
+
def clone_database(
|
406
|
+
self,
|
407
|
+
target_db: str,
|
408
|
+
source_db: str,
|
409
|
+
snapshot_name: Optional[str] = None,
|
410
|
+
if_not_exists: bool = False,
|
411
|
+
executor=None,
|
412
|
+
) -> None:
|
413
|
+
"""
|
414
|
+
Clone a database
|
415
|
+
|
416
|
+
Args::
|
417
|
+
|
418
|
+
target_db: Target database name
|
419
|
+
source_db: Source database name
|
420
|
+
snapshot_name: Optional snapshot name for point-in-time clone
|
421
|
+
if_not_exists: Use IF NOT EXISTS clause
|
422
|
+
executor: Optional executor (e.g., transaction wrapper)
|
423
|
+
|
424
|
+
Raises::
|
425
|
+
|
426
|
+
ConnectionError: If not connected to database
|
427
|
+
CloneError: If clone operation fails
|
428
|
+
"""
|
429
|
+
if not self.client._engine:
|
430
|
+
raise ConnectionError("Not connected to database")
|
431
|
+
|
432
|
+
# Build CLONE DATABASE SQL
|
433
|
+
if_not_exists_clause = "IF NOT EXISTS " if if_not_exists else ""
|
434
|
+
|
435
|
+
if snapshot_name:
|
436
|
+
sql = f"CREATE DATABASE {if_not_exists_clause}{target_db} CLONE {source_db} {{snapshot = '{snapshot_name}'}}"
|
437
|
+
else:
|
438
|
+
sql = f"CREATE DATABASE {if_not_exists_clause}{target_db} CLONE {source_db}"
|
439
|
+
|
440
|
+
try:
|
441
|
+
# Use provided executor or default client execute
|
442
|
+
execute_func = executor.execute if executor else self.client.execute
|
443
|
+
execute_func(sql)
|
444
|
+
except Exception as e:
|
445
|
+
raise CloneError(f"Failed to clone database: {e}") from None
|
446
|
+
|
447
|
+
def clone_table(
|
448
|
+
self,
|
449
|
+
target_table: str,
|
450
|
+
source_table: str,
|
451
|
+
snapshot_name: Optional[str] = None,
|
452
|
+
if_not_exists: bool = False,
|
453
|
+
executor=None,
|
454
|
+
) -> None:
|
455
|
+
"""
|
456
|
+
Clone a table
|
457
|
+
|
458
|
+
Args::
|
459
|
+
|
460
|
+
target_table: Target table name (can include database: db.table)
|
461
|
+
source_table: Source table name (can include database: db.table)
|
462
|
+
snapshot_name: Optional snapshot name for point-in-time clone
|
463
|
+
if_not_exists: Use IF NOT EXISTS clause
|
464
|
+
executor: Optional executor (e.g., transaction wrapper)
|
465
|
+
|
466
|
+
Raises::
|
467
|
+
|
468
|
+
ConnectionError: If not connected to database
|
469
|
+
CloneError: If clone operation fails
|
470
|
+
"""
|
471
|
+
if not self.client._engine:
|
472
|
+
raise ConnectionError("Not connected to database")
|
473
|
+
|
474
|
+
# Build CLONE TABLE SQL
|
475
|
+
if_not_exists_clause = "IF NOT EXISTS " if if_not_exists else ""
|
476
|
+
|
477
|
+
if snapshot_name:
|
478
|
+
sql = (
|
479
|
+
f"CREATE TABLE {if_not_exists_clause}{target_table} "
|
480
|
+
f"CLONE {source_table} {{snapshot = '{snapshot_name}'}}"
|
481
|
+
)
|
482
|
+
else:
|
483
|
+
sql = f"CREATE TABLE {if_not_exists_clause}{target_table} CLONE {source_table}"
|
484
|
+
|
485
|
+
try:
|
486
|
+
# Use provided executor or default client execute
|
487
|
+
execute_func = executor.execute if executor else self.client.execute
|
488
|
+
execute_func(sql)
|
489
|
+
except Exception as e:
|
490
|
+
raise CloneError(f"Failed to clone table: {e}") from None
|
491
|
+
|
492
|
+
def clone_database_with_snapshot(
|
493
|
+
self,
|
494
|
+
target_db: str,
|
495
|
+
source_db: str,
|
496
|
+
snapshot_name: str,
|
497
|
+
if_not_exists: bool = False,
|
498
|
+
executor=None,
|
499
|
+
) -> None:
|
500
|
+
"""
|
501
|
+
Clone a database using a specific snapshot
|
502
|
+
|
503
|
+
Args::
|
504
|
+
|
505
|
+
target_db: Target database name
|
506
|
+
source_db: Source database name
|
507
|
+
snapshot_name: Snapshot name for point-in-time clone
|
508
|
+
if_not_exists: Use IF NOT EXISTS clause
|
509
|
+
executor: Optional executor (e.g., transaction wrapper)
|
510
|
+
|
511
|
+
Raises::
|
512
|
+
|
513
|
+
ConnectionError: If not connected to database
|
514
|
+
CloneError: If clone operation fails or snapshot doesn't exist
|
515
|
+
"""
|
516
|
+
# Verify snapshot exists using snapshot manager
|
517
|
+
if not self.client.snapshots.exists(snapshot_name):
|
518
|
+
raise CloneError(f"Snapshot '{snapshot_name}' does not exist")
|
519
|
+
|
520
|
+
self.clone_database(target_db, source_db, snapshot_name, if_not_exists, executor)
|
521
|
+
|
522
|
+
def clone_table_with_snapshot(
|
523
|
+
self,
|
524
|
+
target_table: str,
|
525
|
+
source_table: str,
|
526
|
+
snapshot_name: str,
|
527
|
+
if_not_exists: bool = False,
|
528
|
+
executor=None,
|
529
|
+
) -> None:
|
530
|
+
"""
|
531
|
+
Clone a table using a specific snapshot
|
532
|
+
|
533
|
+
Args::
|
534
|
+
|
535
|
+
target_table: Target table name (can include database: db.table)
|
536
|
+
source_table: Source table name (can include database: db.table)
|
537
|
+
snapshot_name: Snapshot name for point-in-time clone
|
538
|
+
if_not_exists: Use IF NOT EXISTS clause
|
539
|
+
executor: Optional executor (e.g., transaction wrapper)
|
540
|
+
|
541
|
+
Raises::
|
542
|
+
|
543
|
+
ConnectionError: If not connected to database
|
544
|
+
CloneError: If clone operation fails or snapshot doesn't exist
|
545
|
+
"""
|
546
|
+
# Verify snapshot exists using snapshot manager
|
547
|
+
if not self.client.snapshots.exists(snapshot_name):
|
548
|
+
raise CloneError(f"Snapshot '{snapshot_name}' does not exist")
|
549
|
+
|
550
|
+
self.clone_table(target_table, source_table, snapshot_name, if_not_exists, executor)
|