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/pubsub.py ADDED
@@ -0,0 +1,771 @@
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 - Publish-Subscribe Manager
17
+ Provides Publish-Subscribe functionality for MatrixOne
18
+ """
19
+
20
+ from datetime import datetime
21
+ from typing import List, Optional
22
+
23
+ from .exceptions import PubSubError
24
+
25
+
26
+ class Publication:
27
+ """Publication object"""
28
+
29
+ def __init__(
30
+ self,
31
+ name: str,
32
+ database: str,
33
+ tables: str,
34
+ sub_account: str,
35
+ subscribed_accounts: str,
36
+ created_time: Optional[datetime] = None,
37
+ update_time: Optional[datetime] = None,
38
+ comments: Optional[str] = None,
39
+ ):
40
+ """
41
+ Initialize Publication object
42
+
43
+ Args::
44
+
45
+ name: Publication name
46
+ database: Database name
47
+ tables: Tables (can be '*' for all tables or specific table names)
48
+ sub_account: Subscriber account names (comma-separated)
49
+ subscribed_accounts: Currently subscribed accounts
50
+ created_time: Creation time
51
+ update_time: Last update time
52
+ comments: Publication comments
53
+ """
54
+ self.name = name
55
+ self.database = database
56
+ self.tables = tables
57
+ self.sub_account = sub_account
58
+ self.subscribed_accounts = subscribed_accounts
59
+ self.created_time = created_time
60
+ self.update_time = update_time
61
+ self.comments = comments
62
+
63
+ def __repr__(self):
64
+ return (
65
+ f"<Publication(name='{self.name}', database='{self.database}', "
66
+ f"tables='{self.tables}', sub_account='{self.sub_account}')>"
67
+ )
68
+
69
+
70
+ class Subscription:
71
+ """Subscription object"""
72
+
73
+ def __init__(
74
+ self,
75
+ pub_name: str,
76
+ pub_account: str,
77
+ pub_database: str,
78
+ pub_tables: str,
79
+ sub_name: str,
80
+ pub_comment: Optional[str] = None,
81
+ pub_time: Optional[datetime] = None,
82
+ sub_time: Optional[datetime] = None,
83
+ status: int = 0,
84
+ ):
85
+ """
86
+ Initialize Subscription object
87
+
88
+ Args::
89
+
90
+ pub_name: Publication name
91
+ pub_account: Publisher account name
92
+ pub_database: Publisher database name
93
+ pub_tables: Publisher tables
94
+ pub_comment: Publication comment
95
+ pub_time: Publication creation time
96
+ sub_name: Subscription name
97
+ sub_time: Subscription creation time
98
+ status: Subscription status
99
+ """
100
+ self.pub_name = pub_name
101
+ self.pub_account = pub_account
102
+ self.pub_database = pub_database
103
+ self.pub_tables = pub_tables
104
+ self.pub_comment = pub_comment
105
+ self.pub_time = pub_time
106
+ self.sub_name = sub_name
107
+ self.sub_time = sub_time
108
+ self.status = status
109
+
110
+ def __repr__(self):
111
+ return f"<Subscription(pub_name='{self.pub_name}', sub_name='{self.sub_name}', " f"status={self.status})>"
112
+
113
+
114
+ class PubSubManager:
115
+ """
116
+ Manager for Publish-Subscribe operations in MatrixOne.
117
+
118
+ This class provides comprehensive pub/sub functionality for real-time data
119
+ distribution and event-driven architectures. It supports creating publications
120
+ and subscriptions for database changes, enabling efficient data replication
121
+ and real-time updates across multiple systems.
122
+
123
+ Key Features:
124
+
125
+ - Database publication creation and management
126
+ - Subscription management for real-time updates
127
+ - Event-driven data distribution
128
+ - Integration with MatrixOne's replication system
129
+ - Support for both database and table-level publications
130
+ - Transaction-aware pub/sub operations
131
+
132
+ Supported Operations:
133
+
134
+ - Create and manage database publications
135
+ - Create and manage table publications
136
+ - Create and manage subscriptions
137
+ - List and query publications and subscriptions
138
+ - Monitor pub/sub status and performance
139
+
140
+ Usage Examples::
141
+
142
+ # Initialize pub/sub manager
143
+ pubsub = client.pubsub
144
+
145
+ # Create database publication
146
+ publication = pubsub.create_database_publication(
147
+ name='user_changes',
148
+ database='my_database',
149
+ account='my_account'
150
+ )
151
+
152
+ # Create table publication
153
+ publication = pubsub.create_table_publication(
154
+ name='user_updates',
155
+ database='my_database',
156
+ table='users',
157
+ account='my_account'
158
+ )
159
+
160
+ # Create subscription
161
+ subscription = pubsub.create_subscription(
162
+ name='user_subscription',
163
+ publication='user_changes',
164
+ target_database='replica_database',
165
+ account='my_account'
166
+ )
167
+
168
+ # List publications
169
+ publications = pubsub.list_publications()
170
+
171
+ # List subscriptions
172
+ subscriptions = pubsub.list_subscriptions()
173
+
174
+ # Get pub/sub status
175
+ status = pubsub.get_publication_status('user_changes')
176
+
177
+ Note: Pub/sub functionality requires MatrixOne version 1.0.0 or higher and
178
+ appropriate replication infrastructure. Pub/sub operations may impact
179
+ database performance and should be used judiciously.
180
+ """
181
+
182
+ def __init__(self, client):
183
+ """Initialize PubSubManager with client connection"""
184
+ self._client = client
185
+
186
+ # Publication Operations
187
+
188
+ def create_database_publication(self, name: str, database: str, account: str) -> Publication:
189
+ """
190
+ Create database-level publication
191
+
192
+ Args::
193
+
194
+ name: Publication name
195
+ database: Database name to publish
196
+ account: Subscriber account name
197
+
198
+ Returns::
199
+
200
+ Publication: Created publication object
201
+
202
+ Raises::
203
+
204
+ PubSubError: If publication creation fails
205
+
206
+ Example
207
+
208
+ >>> pub = client.pubsub.create_database_publication("db_pub", "central_db", "acc1")
209
+ """
210
+ try:
211
+ sql = (
212
+ f"CREATE PUBLICATION {self._client._escape_identifier(name)} "
213
+ f"DATABASE {self._client._escape_identifier(database)} "
214
+ f"ACCOUNT {self._client._escape_identifier(account)}"
215
+ )
216
+
217
+ result = self._client.execute(sql)
218
+ if result is None:
219
+ raise PubSubError(f"Failed to create database publication '{name}'") from None
220
+
221
+ # Return publication object (we'll get the actual details via SHOW PUBLICATIONS)
222
+ return self.get_publication(name)
223
+
224
+ except Exception as e:
225
+ raise PubSubError(f"Failed to create database publication '{name}': {e}") from None
226
+
227
+ def create_table_publication(self, name: str, database: str, table: str, account: str) -> Publication:
228
+ """
229
+ Create table-level publication
230
+
231
+ Args::
232
+
233
+ name: Publication name
234
+ database: Database name
235
+ table: Table name to publish
236
+ account: Subscriber account name
237
+
238
+ Returns::
239
+
240
+ Publication: Created publication object
241
+
242
+ Raises::
243
+
244
+ PubSubError: If publication creation fails
245
+
246
+ Example
247
+
248
+ >>> pub = client.pubsub.create_table_publication("table_pub", "central_db", "products", "acc1")
249
+ """
250
+ try:
251
+ sql = (
252
+ f"CREATE PUBLICATION {self._client._escape_identifier(name)} "
253
+ f"DATABASE {self._client._escape_identifier(database)} "
254
+ f"TABLE {self._client._escape_identifier(table)} "
255
+ f"ACCOUNT {self._client._escape_identifier(account)}"
256
+ )
257
+
258
+ result = self._client.execute(sql)
259
+ if result is None:
260
+ raise PubSubError(f"Failed to create table publication '{name}'") from None
261
+
262
+ return self.get_publication(name)
263
+
264
+ except Exception as e:
265
+ raise PubSubError(f"Failed to create table publication '{name}': {e}") from None
266
+
267
+ def get_publication(self, name: str) -> Publication:
268
+ """
269
+ Get publication by name
270
+
271
+ Args::
272
+
273
+ name: Publication name
274
+
275
+ Returns::
276
+
277
+ Publication: Publication object
278
+
279
+ Raises::
280
+
281
+ PubSubError: If publication not found
282
+ """
283
+ try:
284
+ # List all publications and find the one with matching name
285
+ sql = "SHOW PUBLICATIONS"
286
+ result = self._client.execute(sql)
287
+
288
+ if not result or not result.rows:
289
+ raise PubSubError(f"Publication '{name}' not found") from None
290
+
291
+ # Find publication with matching name
292
+ for row in result.rows:
293
+ if row[0] == name: # publication name is in first column
294
+ return self._row_to_publication(row)
295
+
296
+ raise PubSubError(f"Publication '{name}' not found") from None
297
+
298
+ except Exception as e:
299
+ raise PubSubError(f"Failed to get publication '{name}': {e}") from None
300
+
301
+ def list_publications(self, account: Optional[str] = None, database: Optional[str] = None) -> List[Publication]:
302
+ """
303
+ List publications with optional filters
304
+
305
+ Args::
306
+
307
+ account: Filter by subscriber account
308
+ database: Filter by database name
309
+
310
+ Returns::
311
+
312
+ List[Publication]: List of publication objects
313
+ """
314
+ try:
315
+ # For now, just list all publications since WHERE clause syntax may vary
316
+ # In a real implementation, you would need to check the exact syntax supported
317
+ sql = "SHOW PUBLICATIONS"
318
+ result = self._client.execute(sql)
319
+
320
+ if not result or not result.rows:
321
+ return []
322
+
323
+ publications = [self._row_to_publication(row) for row in result.rows]
324
+
325
+ # Apply filters in Python if needed
326
+ if account:
327
+ publications = [pub for pub in publications if account in pub.sub_account]
328
+ if database:
329
+ publications = [pub for pub in publications if pub.database == database]
330
+
331
+ return publications
332
+
333
+ except Exception as e:
334
+ raise PubSubError(f"Failed to list publications: {e}") from None
335
+
336
+ def alter_publication(
337
+ self,
338
+ name: str,
339
+ account: Optional[str] = None,
340
+ database: Optional[str] = None,
341
+ table: Optional[str] = None,
342
+ ) -> Publication:
343
+ """
344
+ Alter publication
345
+
346
+ Args::
347
+
348
+ name: Publication name
349
+ account: New subscriber account
350
+ database: New database name
351
+ table: New table name (for table-level publications)
352
+
353
+ Returns::
354
+
355
+ Publication: Updated publication object
356
+
357
+ Raises::
358
+
359
+ PubSubError: If publication alteration fails
360
+ """
361
+ try:
362
+ # Build ALTER PUBLICATION statement
363
+ parts = [f"ALTER PUBLICATION {self._client._escape_identifier(name)}"]
364
+
365
+ if account:
366
+ parts.append(f"ACCOUNT {self._client._escape_identifier(account)}")
367
+ if database:
368
+ parts.append(f"DATABASE {self._client._escape_identifier(database)}")
369
+ if table:
370
+ parts.append(f"TABLE {self._client._escape_identifier(table)}")
371
+
372
+ sql = " ".join(parts)
373
+ result = self._client.execute(sql)
374
+ if result is None:
375
+ raise PubSubError(f"Failed to alter publication '{name}'") from None
376
+
377
+ return self.get_publication(name)
378
+
379
+ except Exception as e:
380
+ raise PubSubError(f"Failed to alter publication '{name}': {e}") from None
381
+
382
+ def drop_publication(self, name: str) -> bool:
383
+ """
384
+ Drop publication
385
+
386
+ Args::
387
+
388
+ name: Publication name
389
+
390
+ Returns::
391
+
392
+ bool: True if deletion was successful
393
+
394
+ Raises::
395
+
396
+ PubSubError: If publication deletion fails
397
+ """
398
+ try:
399
+ sql = f"DROP PUBLICATION {self._client._escape_identifier(name)}"
400
+ result = self._client.execute(sql)
401
+ return result is not None
402
+
403
+ except Exception as e:
404
+ raise PubSubError(f"Failed to drop publication '{name}': {e}") from None
405
+
406
+ def show_create_publication(self, name: str) -> str:
407
+ """
408
+ Show CREATE PUBLICATION statement for a publication
409
+
410
+ Args::
411
+
412
+ name: Publication name
413
+
414
+ Returns::
415
+
416
+ str: CREATE PUBLICATION statement
417
+
418
+ Raises::
419
+
420
+ PubSubError: If publication not found or retrieval fails
421
+ """
422
+ try:
423
+ sql = f"SHOW CREATE PUBLICATION {self._client._escape_identifier(name)}"
424
+ result = self._client.execute(sql)
425
+
426
+ if not result or not result.rows:
427
+ raise PubSubError(f"Publication '{name}' not found") from None
428
+
429
+ # The result should contain the CREATE statement
430
+ # Assuming the CREATE statement is in the first column
431
+ return result.rows[0][0]
432
+
433
+ except Exception as e:
434
+ raise PubSubError(f"Failed to show create publication '{name}': {e}") from None
435
+
436
+ # Subscription Operations
437
+
438
+ def create_subscription(self, subscription_name: str, publication_name: str, publisher_account: str) -> Subscription:
439
+ """
440
+ Create subscription from publication
441
+
442
+ Args::
443
+
444
+ subscription_name: Name for the subscription database
445
+ publication_name: Name of the publication to subscribe to
446
+ publisher_account: Publisher account name
447
+
448
+ Returns::
449
+
450
+ Subscription: Created subscription object
451
+
452
+ Raises::
453
+
454
+ PubSubError: If subscription creation fails
455
+
456
+ Example
457
+
458
+ >>> sub = client.pubsub.create_subscription("sub_db", "pub_name", "sys")
459
+ """
460
+ try:
461
+ sql = (
462
+ f"CREATE DATABASE {self._client._escape_identifier(subscription_name)} "
463
+ f"FROM {self._client._escape_identifier(publisher_account)} "
464
+ f"PUBLICATION {self._client._escape_identifier(publication_name)}"
465
+ )
466
+
467
+ result = self._client.execute(sql)
468
+ if result is None:
469
+ raise PubSubError(f"Failed to create subscription '{subscription_name}'") from None
470
+
471
+ return self.get_subscription(subscription_name)
472
+
473
+ except Exception as e:
474
+ raise PubSubError(f"Failed to create subscription '{subscription_name}': {e}") from None
475
+
476
+ def get_subscription(self, name: str) -> Subscription:
477
+ """
478
+ Get subscription by name
479
+
480
+ Args::
481
+
482
+ name: Subscription name
483
+
484
+ Returns::
485
+
486
+ Subscription: Subscription object
487
+
488
+ Raises::
489
+
490
+ PubSubError: If subscription not found
491
+ """
492
+ try:
493
+ # List all subscriptions and find the one with matching name
494
+ sql = "SHOW SUBSCRIPTIONS"
495
+ result = self._client.execute(sql)
496
+
497
+ if not result or not result.rows:
498
+ raise PubSubError(f"Subscription '{name}' not found") from None
499
+
500
+ # Find subscription with matching name
501
+ for row in result.rows:
502
+ if len(row) > 6 and row[6] == name: # sub_name is in 7th column (index 6)
503
+ return self._row_to_subscription(row)
504
+
505
+ raise PubSubError(f"Subscription '{name}' not found") from None
506
+
507
+ except Exception as e:
508
+ raise PubSubError(f"Failed to get subscription '{name}': {e}") from None
509
+
510
+ def list_subscriptions(
511
+ self, pub_account: Optional[str] = None, pub_database: Optional[str] = None
512
+ ) -> List[Subscription]:
513
+ """
514
+ List subscriptions with optional filters
515
+
516
+ Args::
517
+
518
+ pub_account: Filter by publisher account
519
+ pub_database: Filter by publisher database
520
+
521
+ Returns::
522
+
523
+ List[Subscription]: List of subscription objects
524
+ """
525
+ try:
526
+ # List all subscriptions since WHERE clause syntax may vary
527
+ sql = "SHOW SUBSCRIPTIONS"
528
+ result = self._client.execute(sql)
529
+
530
+ if not result or not result.rows:
531
+ return []
532
+
533
+ subscriptions = [self._row_to_subscription(row) for row in result.rows]
534
+
535
+ # Apply filters in Python if needed
536
+ if pub_account:
537
+ subscriptions = [sub for sub in subscriptions if sub.pub_account == pub_account]
538
+ if pub_database:
539
+ subscriptions = [sub for sub in subscriptions if sub.pub_database == pub_database]
540
+
541
+ return subscriptions
542
+
543
+ except Exception as e:
544
+ raise PubSubError(f"Failed to list subscriptions: {e}") from None
545
+
546
+ def _row_to_publication(self, row: tuple) -> Publication:
547
+ """Convert database row to Publication object"""
548
+ # Expected columns: publication, database, tables, sub_account, subscribed_accounts,
549
+ # create_time, update_time, comments
550
+ # Based on MatrixOne official documentation:
551
+ # https://docs.matrixorigin.cn/en/v25.2.2.2/MatrixOne/Reference/SQL-Reference/Other/SHOW-Statements/show-publications/
552
+ return Publication(
553
+ name=row[0], # publication
554
+ database=row[1], # database
555
+ tables=row[2], # tables
556
+ sub_account=row[3], # sub_account
557
+ subscribed_accounts=row[4], # subscribed_accounts
558
+ created_time=row[5] if len(row) > 5 else None, # create_time
559
+ update_time=row[6] if len(row) > 6 else None, # update_time
560
+ comments=row[7] if len(row) > 7 else None, # comments
561
+ )
562
+
563
+ def _row_to_subscription(self, row: tuple) -> Subscription:
564
+ """Convert database row to Subscription object"""
565
+ # Expected columns: pub_name, pub_account, pub_database, pub_tables, pub_comment,
566
+ # pub_time, sub_name, sub_time, status
567
+ return Subscription(
568
+ pub_name=row[0],
569
+ pub_account=row[1],
570
+ pub_database=row[2],
571
+ pub_tables=row[3],
572
+ pub_comment=row[4] if len(row) > 4 else None,
573
+ pub_time=row[5] if len(row) > 5 else None,
574
+ sub_name=row[6] if len(row) > 6 else None,
575
+ sub_time=row[7] if len(row) > 7 else None,
576
+ status=row[8] if len(row) > 8 else 0,
577
+ )
578
+
579
+
580
+ class TransactionPubSubManager(PubSubManager):
581
+ """PubSubManager for use within transactions"""
582
+
583
+ def __init__(self, client, transaction_wrapper):
584
+ """Initialize TransactionPubSubManager with client and transaction wrapper"""
585
+ super().__init__(client)
586
+ self._transaction_wrapper = transaction_wrapper
587
+
588
+ def create_database_publication(self, name: str, database: str, account: str) -> Publication:
589
+ """Create database publication within transaction"""
590
+ return self._create_publication_with_executor("database", name, database, account)
591
+
592
+ def create_table_publication(self, name: str, database: str, table: str, account: str) -> Publication:
593
+ """Create table publication within transaction"""
594
+ return self._create_publication_with_executor("table", name, database, account, table)
595
+
596
+ def _create_publication_with_executor(
597
+ self, level: str, name: str, database: str, account: str, table: Optional[str] = None
598
+ ) -> Publication:
599
+ """Create publication with custom executor (for transaction support)"""
600
+ try:
601
+ if level == "database":
602
+ sql = (
603
+ f"CREATE PUBLICATION {self._client._escape_identifier(name)} "
604
+ f"DATABASE {self._client._escape_identifier(database)} "
605
+ f"ACCOUNT {self._client._escape_identifier(account)}"
606
+ )
607
+ elif level == "table":
608
+ sql = (
609
+ f"CREATE PUBLICATION {self._client._escape_identifier(name)} "
610
+ f"DATABASE {self._client._escape_identifier(database)} "
611
+ f"TABLE {self._client._escape_identifier(table)} "
612
+ f"ACCOUNT {self._client._escape_identifier(account)}"
613
+ )
614
+ else:
615
+ raise PubSubError(f"Invalid publication level: {level}") from None
616
+
617
+ result = self._transaction_wrapper.execute(sql)
618
+ if result is None:
619
+ raise PubSubError(f"Failed to create {level} publication '{name}'") from None
620
+
621
+ return self.get_publication(name)
622
+
623
+ except Exception as e:
624
+ raise PubSubError(f"Failed to create {level} publication '{name}': {e}") from None
625
+
626
+ def get_publication(self, name: str) -> Publication:
627
+ """Get publication within transaction"""
628
+ try:
629
+ sql = f"SHOW PUBLICATIONS WHERE pub_name = {self._client._escape_string(name)}"
630
+ result = self._transaction_wrapper.execute(sql)
631
+
632
+ if not result or not result.rows:
633
+ raise PubSubError(f"Publication '{name}' not found") from None
634
+
635
+ row = result.rows[0]
636
+ return self._row_to_publication(row)
637
+
638
+ except Exception as e:
639
+ raise PubSubError(f"Failed to get publication '{name}': {e}") from None
640
+
641
+ def list_publications(self, account: Optional[str] = None, database: Optional[str] = None) -> List[Publication]:
642
+ """List publications within transaction"""
643
+ try:
644
+ # SHOW PUBLICATIONS doesn't support WHERE clause, so we need to list all and filter
645
+ sql = "SHOW PUBLICATIONS"
646
+ result = self._transaction_wrapper.execute(sql)
647
+
648
+ if not result or not result.rows:
649
+ return []
650
+
651
+ publications = []
652
+ for row in result.rows:
653
+ pub = self._row_to_publication(row)
654
+
655
+ # Apply filters
656
+ if account and account not in pub.sub_account:
657
+ continue
658
+ if database and pub.database != database:
659
+ continue
660
+
661
+ publications.append(pub)
662
+
663
+ return publications
664
+
665
+ except Exception as e:
666
+ raise PubSubError(f"Failed to list publications: {e}") from None
667
+
668
+ def alter_publication(
669
+ self,
670
+ name: str,
671
+ account: Optional[str] = None,
672
+ database: Optional[str] = None,
673
+ table: Optional[str] = None,
674
+ ) -> Publication:
675
+ """Alter publication within transaction"""
676
+ try:
677
+ # Build ALTER PUBLICATION statement
678
+ parts = [f"ALTER PUBLICATION {self._client._escape_identifier(name)}"]
679
+
680
+ if account:
681
+ parts.append(f"ACCOUNT {self._client._escape_identifier(account)}")
682
+ if database:
683
+ parts.append(f"DATABASE {self._client._escape_identifier(database)}")
684
+ if table:
685
+ parts.append(f"TABLE {self._client._escape_identifier(table)}")
686
+
687
+ sql = " ".join(parts)
688
+ result = self._transaction_wrapper.execute(sql)
689
+ if result is None:
690
+ raise PubSubError(f"Failed to alter publication '{name}'") from None
691
+
692
+ return self.get_publication(name)
693
+
694
+ except Exception as e:
695
+ raise PubSubError(f"Failed to alter publication '{name}': {e}") from None
696
+
697
+ def drop_publication(self, name: str) -> bool:
698
+ """Drop publication within transaction"""
699
+ try:
700
+ sql = f"DROP PUBLICATION {self._client._escape_identifier(name)}"
701
+ result = self._transaction_wrapper.execute(sql)
702
+ return result is not None
703
+
704
+ except Exception as e:
705
+ raise PubSubError(f"Failed to drop publication '{name}': {e}") from None
706
+
707
+ def create_subscription(self, subscription_name: str, publication_name: str, publisher_account: str) -> Subscription:
708
+ """Create subscription within transaction"""
709
+ try:
710
+ sql = (
711
+ f"CREATE DATABASE {self._client._escape_identifier(subscription_name)} "
712
+ f"FROM {self._client._escape_identifier(publisher_account)} "
713
+ f"PUBLICATION {self._client._escape_identifier(publication_name)}"
714
+ )
715
+
716
+ result = self._transaction_wrapper.execute(sql)
717
+ if result is None:
718
+ raise PubSubError(f"Failed to create subscription '{subscription_name}'") from None
719
+
720
+ return self.get_subscription(subscription_name)
721
+
722
+ except Exception as e:
723
+ raise PubSubError(f"Failed to create subscription '{subscription_name}': {e}") from None
724
+
725
+ def get_subscription(self, name: str) -> Subscription:
726
+ """Get subscription within transaction"""
727
+ try:
728
+ # SHOW SUBSCRIPTIONS doesn't support WHERE clause, so we need to list all and filter
729
+ sql = "SHOW SUBSCRIPTIONS"
730
+ result = self._transaction_wrapper.execute(sql)
731
+
732
+ if not result or not result.rows:
733
+ raise PubSubError(f"Subscription '{name}' not found") from None
734
+
735
+ # Find subscription with matching name
736
+ for row in result.rows:
737
+ if row[6] == name: # sub_name is in 7th column (index 6)
738
+ return self._row_to_subscription(row)
739
+
740
+ raise PubSubError(f"Subscription '{name}' not found") from None
741
+
742
+ except Exception as e:
743
+ raise PubSubError(f"Failed to get subscription '{name}': {e}") from None
744
+
745
+ def list_subscriptions(
746
+ self, pub_account: Optional[str] = None, pub_database: Optional[str] = None
747
+ ) -> List[Subscription]:
748
+ """List subscriptions within transaction"""
749
+ try:
750
+ conditions = []
751
+
752
+ if pub_account:
753
+ conditions.append(f"pub_account = {self._client._escape_string(pub_account)}")
754
+ if pub_database:
755
+ conditions.append(f"pub_database = {self._client._escape_string(pub_database)}")
756
+
757
+ if conditions:
758
+ where_clause = " WHERE " + " AND ".join(conditions)
759
+ else:
760
+ where_clause = ""
761
+
762
+ sql = f"SHOW SUBSCRIPTIONS{where_clause}"
763
+ result = self._transaction_wrapper.execute(sql)
764
+
765
+ if not result or not result.rows:
766
+ return []
767
+
768
+ return [self._row_to_subscription(row) for row in result.rows]
769
+
770
+ except Exception as e:
771
+ raise PubSubError(f"Failed to list subscriptions: {e}") from None