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/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
|