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.
Files changed (122) hide show
  1. matrixone/__init__.py +155 -0
  2. matrixone/account.py +723 -0
  3. matrixone/async_client.py +3913 -0
  4. matrixone/async_metadata_manager.py +311 -0
  5. matrixone/async_orm.py +123 -0
  6. matrixone/async_vector_index_manager.py +633 -0
  7. matrixone/base_client.py +208 -0
  8. matrixone/client.py +4672 -0
  9. matrixone/config.py +452 -0
  10. matrixone/connection_hooks.py +286 -0
  11. matrixone/exceptions.py +89 -0
  12. matrixone/logger.py +782 -0
  13. matrixone/metadata.py +820 -0
  14. matrixone/moctl.py +219 -0
  15. matrixone/orm.py +2277 -0
  16. matrixone/pitr.py +646 -0
  17. matrixone/pubsub.py +771 -0
  18. matrixone/restore.py +411 -0
  19. matrixone/search_vector_index.py +1176 -0
  20. matrixone/snapshot.py +550 -0
  21. matrixone/sql_builder.py +844 -0
  22. matrixone/sqlalchemy_ext/__init__.py +161 -0
  23. matrixone/sqlalchemy_ext/adapters.py +163 -0
  24. matrixone/sqlalchemy_ext/dialect.py +534 -0
  25. matrixone/sqlalchemy_ext/fulltext_index.py +895 -0
  26. matrixone/sqlalchemy_ext/fulltext_search.py +1686 -0
  27. matrixone/sqlalchemy_ext/hnsw_config.py +194 -0
  28. matrixone/sqlalchemy_ext/ivf_config.py +252 -0
  29. matrixone/sqlalchemy_ext/table_builder.py +351 -0
  30. matrixone/sqlalchemy_ext/vector_index.py +1721 -0
  31. matrixone/sqlalchemy_ext/vector_type.py +948 -0
  32. matrixone/version.py +580 -0
  33. matrixone_python_sdk-0.1.0.dist-info/METADATA +706 -0
  34. matrixone_python_sdk-0.1.0.dist-info/RECORD +122 -0
  35. matrixone_python_sdk-0.1.0.dist-info/WHEEL +5 -0
  36. matrixone_python_sdk-0.1.0.dist-info/entry_points.txt +5 -0
  37. matrixone_python_sdk-0.1.0.dist-info/licenses/LICENSE +200 -0
  38. matrixone_python_sdk-0.1.0.dist-info/top_level.txt +2 -0
  39. tests/__init__.py +19 -0
  40. tests/offline/__init__.py +20 -0
  41. tests/offline/conftest.py +77 -0
  42. tests/offline/test_account.py +703 -0
  43. tests/offline/test_async_client_query_comprehensive.py +1218 -0
  44. tests/offline/test_basic.py +54 -0
  45. tests/offline/test_case_sensitivity.py +227 -0
  46. tests/offline/test_connection_hooks_offline.py +287 -0
  47. tests/offline/test_dialect_schema_handling.py +609 -0
  48. tests/offline/test_explain_methods.py +346 -0
  49. tests/offline/test_filter_logical_in.py +237 -0
  50. tests/offline/test_fulltext_search_comprehensive.py +795 -0
  51. tests/offline/test_ivf_config.py +249 -0
  52. tests/offline/test_join_methods.py +281 -0
  53. tests/offline/test_join_sqlalchemy_compatibility.py +276 -0
  54. tests/offline/test_logical_in_method.py +237 -0
  55. tests/offline/test_matrixone_version_parsing.py +264 -0
  56. tests/offline/test_metadata_offline.py +557 -0
  57. tests/offline/test_moctl.py +300 -0
  58. tests/offline/test_moctl_simple.py +251 -0
  59. tests/offline/test_model_support_offline.py +359 -0
  60. tests/offline/test_model_support_simple.py +225 -0
  61. tests/offline/test_pinecone_filter_offline.py +377 -0
  62. tests/offline/test_pitr.py +585 -0
  63. tests/offline/test_pubsub.py +712 -0
  64. tests/offline/test_query_update.py +283 -0
  65. tests/offline/test_restore.py +445 -0
  66. tests/offline/test_snapshot_comprehensive.py +384 -0
  67. tests/offline/test_sql_escaping_edge_cases.py +551 -0
  68. tests/offline/test_sqlalchemy_integration.py +382 -0
  69. tests/offline/test_sqlalchemy_vector_integration.py +434 -0
  70. tests/offline/test_table_builder.py +198 -0
  71. tests/offline/test_unified_filter.py +398 -0
  72. tests/offline/test_unified_transaction.py +495 -0
  73. tests/offline/test_vector_index.py +238 -0
  74. tests/offline/test_vector_operations.py +688 -0
  75. tests/offline/test_vector_type.py +174 -0
  76. tests/offline/test_version_core.py +328 -0
  77. tests/offline/test_version_management.py +372 -0
  78. tests/offline/test_version_standalone.py +652 -0
  79. tests/online/__init__.py +20 -0
  80. tests/online/conftest.py +216 -0
  81. tests/online/test_account_management.py +194 -0
  82. tests/online/test_advanced_features.py +344 -0
  83. tests/online/test_async_client_interfaces.py +330 -0
  84. tests/online/test_async_client_online.py +285 -0
  85. tests/online/test_async_model_insert_online.py +293 -0
  86. tests/online/test_async_orm_online.py +300 -0
  87. tests/online/test_async_simple_query_online.py +802 -0
  88. tests/online/test_async_transaction_simple_query.py +300 -0
  89. tests/online/test_basic_connection.py +130 -0
  90. tests/online/test_client_online.py +238 -0
  91. tests/online/test_config.py +90 -0
  92. tests/online/test_config_validation.py +123 -0
  93. tests/online/test_connection_hooks_new_online.py +217 -0
  94. tests/online/test_dialect_schema_handling_online.py +331 -0
  95. tests/online/test_filter_logical_in_online.py +374 -0
  96. tests/online/test_fulltext_comprehensive.py +1773 -0
  97. tests/online/test_fulltext_label_online.py +433 -0
  98. tests/online/test_fulltext_search_online.py +842 -0
  99. tests/online/test_ivf_stats_online.py +506 -0
  100. tests/online/test_logger_integration.py +311 -0
  101. tests/online/test_matrixone_query_orm.py +540 -0
  102. tests/online/test_metadata_online.py +579 -0
  103. tests/online/test_model_insert_online.py +255 -0
  104. tests/online/test_mysql_driver_validation.py +213 -0
  105. tests/online/test_orm_advanced_features.py +2022 -0
  106. tests/online/test_orm_cte_integration.py +269 -0
  107. tests/online/test_orm_online.py +270 -0
  108. tests/online/test_pinecone_filter.py +708 -0
  109. tests/online/test_pubsub_operations.py +352 -0
  110. tests/online/test_query_methods.py +225 -0
  111. tests/online/test_query_update_online.py +433 -0
  112. tests/online/test_search_vector_index.py +557 -0
  113. tests/online/test_simple_fulltext_online.py +915 -0
  114. tests/online/test_snapshot_comprehensive.py +998 -0
  115. tests/online/test_sqlalchemy_engine_integration.py +336 -0
  116. tests/online/test_sqlalchemy_integration.py +425 -0
  117. tests/online/test_transaction_contexts.py +1219 -0
  118. tests/online/test_transaction_insert_methods.py +356 -0
  119. tests/online/test_transaction_query_methods.py +288 -0
  120. tests/online/test_unified_filter_online.py +529 -0
  121. tests/online/test_vector_comprehensive.py +706 -0
  122. tests/online/test_version_management.py +291 -0
matrixone/pitr.py ADDED
@@ -0,0 +1,646 @@
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 Python SDK - PITR Manager
17
+ Provides Point-in-Time Recovery functionality for MatrixOne
18
+ """
19
+
20
+ from datetime import datetime
21
+ from typing import List, Optional
22
+
23
+ from .exceptions import PitrError
24
+ from .version import requires_version
25
+
26
+
27
+ class Pitr:
28
+ """PITR (Point-in-Time Recovery) object"""
29
+
30
+ def __init__(
31
+ self,
32
+ name: str,
33
+ created_time: datetime,
34
+ modified_time: datetime,
35
+ level: str,
36
+ account_name: Optional[str] = None,
37
+ database_name: Optional[str] = None,
38
+ table_name: Optional[str] = None,
39
+ range_value: int = 1,
40
+ range_unit: str = "d",
41
+ ):
42
+ """
43
+ Initialize PITR object
44
+
45
+ Args::
46
+
47
+ name: PITR name
48
+ created_time: Creation time
49
+ modified_time: Last modification time
50
+ level: PITR level (cluster, account, database, table)
51
+ account_name: Account name (for account/database/table level)
52
+ database_name: Database name (for database/table level)
53
+ table_name: Table name (for table level)
54
+ range_value: Time range value (1-100)
55
+ range_unit: Time range unit (h, d, mo, y)
56
+ """
57
+ self.name = name
58
+ self.created_time = created_time
59
+ self.modified_time = modified_time
60
+ self.level = level
61
+ self.account_name = account_name
62
+ self.database_name = database_name
63
+ self.table_name = table_name
64
+ self.range_value = range_value
65
+ self.range_unit = range_unit
66
+
67
+ def __repr__(self):
68
+ return f"<Pitr(name='{self.name}', level='{self.level}', " f"range={self.range_value}{self.range_unit})>"
69
+
70
+
71
+ class PitrManager:
72
+ """
73
+ Manager for Point-in-Time Recovery (PITR) operations in MatrixOne.
74
+
75
+ This class provides comprehensive PITR functionality for recovering data
76
+ to specific points in time. PITR allows you to restore databases or
77
+ tables to their state at any point in time, providing granular recovery
78
+ capabilities for data protection and disaster recovery.
79
+
80
+ Key Features:
81
+
82
+ - Point-in-time recovery for databases and tables
83
+ - Recovery to specific timestamps
84
+ - Integration with backup and snapshot systems
85
+ - Transaction-aware recovery operations
86
+ - Support for both cluster and table-level recovery
87
+
88
+ Supported Recovery Levels:
89
+ - CLUSTER: Full cluster recovery to a specific point in time
90
+ - DATABASE: Database-level recovery to a specific point in time
91
+ - TABLE: Table-level recovery to a specific point in time
92
+
93
+ Usage Examples::
94
+
95
+ # Initialize PITR manager
96
+ pitr = client.pitr
97
+
98
+ # Recover database to specific timestamp
99
+ pitr.recover_database(
100
+ database='my_database',
101
+ timestamp='2024-01-15 10:30:00',
102
+ target_database='recovered_database'
103
+ )
104
+
105
+ # Recover table to specific timestamp
106
+ pitr.recover_table(
107
+ database='my_database',
108
+ table='users',
109
+ timestamp='2024-01-15 10:30:00',
110
+ target_database='recovered_database',
111
+ target_table='recovered_users'
112
+ )
113
+
114
+ # List available recovery points
115
+ recovery_points = pitr.list_recovery_points('my_database')
116
+
117
+ # Get recovery status
118
+ status = pitr.get_recovery_status('recovery_job_id')
119
+
120
+ Note: PITR functionality requires MatrixOne version 1.0.0 or higher and
121
+ appropriate backup infrastructure. Recovery operations may take significant
122
+ time depending on the amount of data and the target timestamp.
123
+ """
124
+
125
+ def __init__(self, client):
126
+ """Initialize PitrManager with client connection"""
127
+ self._client = client
128
+
129
+ @requires_version(
130
+ min_version="1.0.0",
131
+ feature_name="pitr_cluster_level",
132
+ description="Cluster-level Point-in-Time Recovery functionality",
133
+ alternative="Use snapshot restore instead",
134
+ )
135
+ def create_cluster_pitr(self, name: str, range_value: int = 1, range_unit: str = "d") -> Pitr:
136
+ """
137
+ Create cluster-level PITR
138
+
139
+ Args::
140
+
141
+ name: PITR name
142
+ range_value: Time range value (1-100)
143
+ range_unit: Time range unit (h, d, mo, y)
144
+
145
+ Returns::
146
+
147
+ Pitr: Created PITR object
148
+
149
+ Raises::
150
+
151
+ PitrError: If PITR creation fails
152
+
153
+ Example
154
+
155
+ >>> pitr = client.pitr.create_cluster_pitr("cluster_pitr1", 1, "d")
156
+ """
157
+ try:
158
+ self._validate_range(range_value, range_unit)
159
+
160
+ sql = f"CREATE PITR {self._client._escape_identifier(name)} " f"FOR CLUSTER RANGE {range_value} '{range_unit}'"
161
+
162
+ result = self._client.execute(sql)
163
+ if result is None:
164
+ raise PitrError(f"Failed to create cluster PITR '{name}'") from None
165
+
166
+ # Return PITR object (we'll get the actual details via SHOW PITR)
167
+ return self.get(name)
168
+
169
+ except Exception as e:
170
+ raise PitrError(f"Failed to create cluster PITR '{name}': {e}") from None
171
+
172
+ def create_account_pitr(
173
+ self,
174
+ name: str,
175
+ account_name: Optional[str] = None,
176
+ range_value: int = 1,
177
+ range_unit: str = "d",
178
+ ) -> Pitr:
179
+ """
180
+ Create account-level PITR
181
+
182
+ Args::
183
+
184
+ name: PITR name
185
+ account_name: Account name (None for current account)
186
+ range_value: Time range value (1-100)
187
+ range_unit: Time range unit (h, d, mo, y)
188
+
189
+ Returns::
190
+
191
+ Pitr: Created PITR object
192
+
193
+ Raises::
194
+
195
+ PitrError: If PITR creation fails
196
+
197
+ Example
198
+
199
+ >>> # For current account
200
+ >>> pitr = client.pitr.create_account_pitr("account_pitr1", range_value=2, range_unit="h")
201
+ >>>
202
+ >>> # For specific account (cluster admin only)
203
+ >>> pitr = client.pitr.create_account_pitr("account_pitr1", "acc1", 1, "d")
204
+ """
205
+ try:
206
+ self._validate_range(range_value, range_unit)
207
+
208
+ if account_name:
209
+ sql = (
210
+ f"CREATE PITR {self._client._escape_identifier(name)} "
211
+ f"FOR ACCOUNT {self._client._escape_identifier(account_name)} "
212
+ f"RANGE {range_value} '{range_unit}'"
213
+ )
214
+ else:
215
+ sql = (
216
+ f"CREATE PITR {self._client._escape_identifier(name)} " f"FOR ACCOUNT RANGE {range_value} '{range_unit}'"
217
+ )
218
+
219
+ result = self._client.execute(sql)
220
+ if result is None:
221
+ raise PitrError(f"Failed to create account PITR '{name}'") from None
222
+
223
+ return self.get(name)
224
+
225
+ except Exception as e:
226
+ raise PitrError(f"Failed to create account PITR '{name}': {e}") from None
227
+
228
+ def create_database_pitr(self, name: str, database_name: str, range_value: int = 1, range_unit: str = "d") -> Pitr:
229
+ """
230
+ Create database-level PITR
231
+
232
+ Args::
233
+
234
+ name: PITR name
235
+ database_name: Database name
236
+ range_value: Time range value (1-100)
237
+ range_unit: Time range unit (h, d, mo, y)
238
+
239
+ Returns::
240
+
241
+ Pitr: Created PITR object
242
+
243
+ Raises::
244
+
245
+ PitrError: If PITR creation fails
246
+
247
+ Example
248
+
249
+ >>> pitr = client.pitr.create_database_pitr("db_pitr1", "db1", 1, "y")
250
+ """
251
+ try:
252
+ self._validate_range(range_value, range_unit)
253
+
254
+ sql = (
255
+ f"CREATE PITR {self._client._escape_identifier(name)} "
256
+ f"FOR DATABASE {self._client._escape_identifier(database_name)} "
257
+ f"RANGE {range_value} '{range_unit}'"
258
+ )
259
+
260
+ result = self._client.execute(sql)
261
+ if result is None:
262
+ raise PitrError(f"Failed to create database PITR '{name}'") from None
263
+
264
+ return self.get(name)
265
+
266
+ except Exception as e:
267
+ raise PitrError(f"Failed to create database PITR '{name}': {e}") from None
268
+
269
+ def create_table_pitr(
270
+ self,
271
+ name: str,
272
+ database_name: str,
273
+ table_name: str,
274
+ range_value: int = 1,
275
+ range_unit: str = "d",
276
+ ) -> Pitr:
277
+ """
278
+ Create table-level PITR
279
+
280
+ Args::
281
+
282
+ name: PITR name
283
+ database_name: Database name
284
+ table_name: Table name
285
+ range_value: Time range value (1-100)
286
+ range_unit: Time range unit (h, d, mo, y)
287
+
288
+ Returns::
289
+
290
+ Pitr: Created PITR object
291
+
292
+ Raises::
293
+
294
+ PitrError: If PITR creation fails
295
+
296
+ Example
297
+
298
+ >>> pitr = client.pitr.create_table_pitr("tab_pitr1", "db1", "t1", 1, "y")
299
+ """
300
+ try:
301
+ self._validate_range(range_value, range_unit)
302
+
303
+ sql = (
304
+ f"CREATE PITR {self._client._escape_identifier(name)} "
305
+ f"FOR TABLE {self._client._escape_identifier(database_name)} "
306
+ f"{self._client._escape_identifier(table_name)} "
307
+ f"RANGE {range_value} '{range_unit}'"
308
+ )
309
+
310
+ result = self._client.execute(sql)
311
+ if result is None:
312
+ raise PitrError(f"Failed to create table PITR '{name}'") from None
313
+
314
+ return self.get(name)
315
+
316
+ except Exception as e:
317
+ raise PitrError(f"Failed to create table PITR '{name}': {e}") from None
318
+
319
+ def get(self, name: str) -> Pitr:
320
+ """
321
+ Get PITR by name
322
+
323
+ Args::
324
+
325
+ name: PITR name
326
+
327
+ Returns::
328
+
329
+ Pitr: PITR object
330
+
331
+ Raises::
332
+
333
+ PitrError: If PITR not found
334
+ """
335
+ try:
336
+ sql = f"SHOW PITR WHERE pitr_name = {self._client._escape_string(name)}"
337
+ result = self._client.execute(sql)
338
+
339
+ if not result or not result.rows:
340
+ raise PitrError(f"PITR '{name}' not found") from None
341
+
342
+ row = result.rows[0]
343
+ return self._row_to_pitr(row)
344
+
345
+ except Exception as e:
346
+ raise PitrError(f"Failed to get PITR '{name}': {e}") from None
347
+
348
+ def list(
349
+ self,
350
+ level: Optional[str] = None,
351
+ account_name: Optional[str] = None,
352
+ database_name: Optional[str] = None,
353
+ table_name: Optional[str] = None,
354
+ ) -> List[Pitr]:
355
+ """
356
+ List PITRs with optional filters
357
+
358
+ Args::
359
+
360
+ level: Filter by PITR level (cluster, account, database, table)
361
+ account_name: Filter by account name
362
+ database_name: Filter by database name
363
+ table_name: Filter by table name
364
+
365
+ Returns::
366
+
367
+ List[Pitr]: List of PITR objects
368
+ """
369
+ try:
370
+ conditions = []
371
+
372
+ if level:
373
+ conditions.append(f"pitr_level = {self._client._escape_string(level)}")
374
+ if account_name:
375
+ conditions.append(f"account_name = {self._client._escape_string(account_name)}")
376
+ if database_name:
377
+ conditions.append(f"database_name = {self._client._escape_string(database_name)}")
378
+ if table_name:
379
+ conditions.append(f"table_name = {self._client._escape_string(table_name)}")
380
+
381
+ if conditions:
382
+ where_clause = " WHERE " + " AND ".join(conditions)
383
+ else:
384
+ where_clause = ""
385
+
386
+ sql = f"SHOW PITR{where_clause}"
387
+ result = self._client.execute(sql)
388
+
389
+ if not result or not result.rows:
390
+ return []
391
+
392
+ return [self._row_to_pitr(row) for row in result.rows]
393
+
394
+ except Exception as e:
395
+ raise PitrError(f"Failed to list PITRs: {e}") from None
396
+
397
+ def alter(self, name: str, range_value: int, range_unit: str) -> Pitr:
398
+ """
399
+ Alter PITR range
400
+
401
+ Args::
402
+
403
+ name: PITR name
404
+ range_value: New time range value (1-100)
405
+ range_unit: New time range unit (h, d, mo, y)
406
+
407
+ Returns::
408
+
409
+ Pitr: Updated PITR object
410
+
411
+ Raises::
412
+
413
+ PitrError: If PITR alteration fails
414
+ """
415
+ try:
416
+ self._validate_range(range_value, range_unit)
417
+
418
+ sql = f"ALTER PITR {self._client._escape_identifier(name)} " f"RANGE {range_value} '{range_unit}'"
419
+
420
+ result = self._client.execute(sql)
421
+ if result is None:
422
+ raise PitrError(f"Failed to alter PITR '{name}'") from None
423
+
424
+ return self.get(name)
425
+
426
+ except Exception as e:
427
+ raise PitrError(f"Failed to alter PITR '{name}': {e}") from None
428
+
429
+ def delete(self, name: str) -> bool:
430
+ """
431
+ Delete PITR
432
+
433
+ Args::
434
+
435
+ name: PITR name
436
+
437
+ Returns::
438
+
439
+ bool: True if deletion was successful
440
+
441
+ Raises::
442
+
443
+ PitrError: If PITR deletion fails
444
+ """
445
+ try:
446
+ sql = f"DROP PITR {self._client._escape_identifier(name)}"
447
+ result = self._client.execute(sql)
448
+ return result is not None
449
+
450
+ except Exception as e:
451
+ raise PitrError(f"Failed to delete PITR '{name}': {e}") from None
452
+
453
+ def _validate_range(self, range_value: int, range_unit: str) -> None:
454
+ """Validate PITR range parameters"""
455
+ if not (1 <= range_value <= 100):
456
+ raise PitrError("Range value must be between 1 and 100") from None
457
+
458
+ valid_units = ["h", "d", "mo", "y"]
459
+ if range_unit not in valid_units:
460
+ raise PitrError(f"Range unit must be one of: {', '.join(valid_units)}") from None
461
+
462
+ def _row_to_pitr(self, row: tuple) -> Pitr:
463
+ """Convert database row to Pitr object"""
464
+ # Expected columns: pitr_name, created_time, modified_time, pitr_level,
465
+ # account_name, database_name, table_name, pitr_length, pitr_unit
466
+ return Pitr(
467
+ name=row[0],
468
+ created_time=row[1],
469
+ modified_time=row[2],
470
+ level=row[3],
471
+ account_name=row[4] if row[4] != "*" else None,
472
+ database_name=row[5] if row[5] != "*" else None,
473
+ table_name=row[6] if row[6] != "*" else None,
474
+ range_value=row[7],
475
+ range_unit=row[8],
476
+ )
477
+
478
+
479
+ class TransactionPitrManager(PitrManager):
480
+ """PitrManager for use within transactions"""
481
+
482
+ def __init__(self, client, transaction_wrapper):
483
+ """Initialize TransactionPitrManager with client and transaction wrapper"""
484
+ super().__init__(client)
485
+ self._transaction_wrapper = transaction_wrapper
486
+
487
+ def create_cluster_pitr(self, name: str, range_value: int = 1, range_unit: str = "d") -> Pitr:
488
+ """Create cluster PITR within transaction"""
489
+ return self._create_pitr_with_executor("cluster", name, range_value, range_unit)
490
+
491
+ def create_account_pitr(
492
+ self,
493
+ name: str,
494
+ account_name: Optional[str] = None,
495
+ range_value: int = 1,
496
+ range_unit: str = "d",
497
+ ) -> Pitr:
498
+ """Create account PITR within transaction"""
499
+ return self._create_pitr_with_executor("account", name, range_value, range_unit, account_name)
500
+
501
+ def create_database_pitr(self, name: str, database_name: str, range_value: int = 1, range_unit: str = "d") -> Pitr:
502
+ """Create database PITR within transaction"""
503
+ return self._create_pitr_with_executor("database", name, range_value, range_unit, None, database_name)
504
+
505
+ def create_table_pitr(
506
+ self,
507
+ name: str,
508
+ database_name: str,
509
+ table_name: str,
510
+ range_value: int = 1,
511
+ range_unit: str = "d",
512
+ ) -> Pitr:
513
+ """Create table PITR within transaction"""
514
+ return self._create_pitr_with_executor("table", name, range_value, range_unit, None, database_name, table_name)
515
+
516
+ def _create_pitr_with_executor(
517
+ self,
518
+ level: str,
519
+ name: str,
520
+ range_value: int,
521
+ range_unit: str,
522
+ account_name: Optional[str] = None,
523
+ database_name: Optional[str] = None,
524
+ table_name: Optional[str] = None,
525
+ ) -> Pitr:
526
+ """Create PITR with custom executor (for transaction support)"""
527
+ try:
528
+ self._validate_range(range_value, range_unit)
529
+
530
+ if level == "cluster":
531
+ sql = (
532
+ f"CREATE PITR {self._client._escape_identifier(name)} " f"FOR CLUSTER RANGE {range_value} '{range_unit}'"
533
+ )
534
+ elif level == "account":
535
+ if account_name:
536
+ sql = (
537
+ f"CREATE PITR {self._client._escape_identifier(name)} "
538
+ f"FOR ACCOUNT {self._client._escape_identifier(account_name)} "
539
+ f"RANGE {range_value} '{range_unit}'"
540
+ )
541
+ else:
542
+ sql = (
543
+ f"CREATE PITR {self._client._escape_identifier(name)} "
544
+ f"FOR ACCOUNT RANGE {range_value} '{range_unit}'"
545
+ )
546
+ elif level == "database":
547
+ sql = (
548
+ f"CREATE PITR {self._client._escape_identifier(name)} "
549
+ f"FOR DATABASE {self._client._escape_identifier(database_name)} "
550
+ f"RANGE {range_value} '{range_unit}'"
551
+ )
552
+ elif level == "table":
553
+ sql = (
554
+ f"CREATE PITR {self._client._escape_identifier(name)} "
555
+ f"FOR TABLE {self._client._escape_identifier(database_name)} "
556
+ f"TABLE {self._client._escape_identifier(table_name)} "
557
+ f"RANGE {range_value} '{range_unit}'"
558
+ )
559
+ else:
560
+ raise PitrError(f"Invalid PITR level: {level}") from None
561
+
562
+ result = self._transaction_wrapper.execute(sql)
563
+ if result is None:
564
+ raise PitrError(f"Failed to create {level} PITR '{name}'") from None
565
+
566
+ return self.get(name)
567
+
568
+ except Exception as e:
569
+ raise PitrError(f"Failed to create {level} PITR '{name}': {e}") from None
570
+
571
+ def get(self, name: str) -> Pitr:
572
+ """Get PITR within transaction"""
573
+ try:
574
+ sql = f"SHOW PITR WHERE pitr_name = {self._client._escape_string(name)}"
575
+ result = self._transaction_wrapper.execute(sql)
576
+
577
+ if not result or not result.rows:
578
+ raise PitrError(f"PITR '{name}' not found") from None
579
+
580
+ row = result.rows[0]
581
+ return self._row_to_pitr(row)
582
+
583
+ except Exception as e:
584
+ raise PitrError(f"Failed to get PITR '{name}': {e}") from None
585
+
586
+ def list(
587
+ self,
588
+ level: Optional[str] = None,
589
+ account_name: Optional[str] = None,
590
+ database_name: Optional[str] = None,
591
+ table_name: Optional[str] = None,
592
+ ) -> List[Pitr]:
593
+ """List PITRs within transaction"""
594
+ try:
595
+ conditions = []
596
+
597
+ if level:
598
+ conditions.append(f"pitr_level = {self._client._escape_string(level)}")
599
+ if account_name:
600
+ conditions.append(f"account_name = {self._client._escape_string(account_name)}")
601
+ if database_name:
602
+ conditions.append(f"database_name = {self._client._escape_string(database_name)}")
603
+ if table_name:
604
+ conditions.append(f"table_name = {self._client._escape_string(table_name)}")
605
+
606
+ if conditions:
607
+ where_clause = " WHERE " + " AND ".join(conditions)
608
+ else:
609
+ where_clause = ""
610
+
611
+ sql = f"SHOW PITR{where_clause}"
612
+ result = self._transaction_wrapper.execute(sql)
613
+
614
+ if not result or not result.rows:
615
+ return []
616
+
617
+ return [self._row_to_pitr(row) for row in result.rows]
618
+
619
+ except Exception as e:
620
+ raise PitrError(f"Failed to list PITRs: {e}") from None
621
+
622
+ def alter(self, name: str, range_value: int, range_unit: str) -> Pitr:
623
+ """Alter PITR within transaction"""
624
+ try:
625
+ self._validate_range(range_value, range_unit)
626
+
627
+ sql = f"ALTER PITR {self._client._escape_identifier(name)} " f"RANGE {range_value} '{range_unit}'"
628
+
629
+ result = self._transaction_wrapper.execute(sql)
630
+ if result is None:
631
+ raise PitrError(f"Failed to alter PITR '{name}'") from None
632
+
633
+ return self.get(name)
634
+
635
+ except Exception as e:
636
+ raise PitrError(f"Failed to alter PITR '{name}': {e}") from None
637
+
638
+ def delete(self, name: str) -> bool:
639
+ """Delete PITR within transaction"""
640
+ try:
641
+ sql = f"DROP PITR {self._client._escape_identifier(name)}"
642
+ result = self._transaction_wrapper.execute(sql)
643
+ return result is not None
644
+
645
+ except Exception as e:
646
+ raise PitrError(f"Failed to delete PITR '{name}': {e}") from None