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
@@ -0,0 +1,1219 @@
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
+ Test that main interfaces work in both transaction contexts:
17
+ 1. MatrixOne Client transaction context: with client.transaction() as tx:
18
+ 2. SQLAlchemy transaction context: with client.get_sqlalchemy_engine().begin() as conn:
19
+ """
20
+
21
+ import pytest
22
+ import pytest_asyncio
23
+ from matrixone import Client, AsyncClient
24
+ from contextlib import contextmanager
25
+ from .test_config import online_config
26
+ from matrixone.sqlalchemy_ext import boolean_match
27
+
28
+
29
+ class TestSyncTransactionContexts:
30
+ """Test sync client transaction contexts"""
31
+
32
+ @pytest.fixture(scope="function")
33
+ def sync_client_setup(self):
34
+ """Setup sync client for testing"""
35
+ client = Client()
36
+ host, port, user, password, database = online_config.get_connection_params()
37
+ client.connect(host, port, user, password, database)
38
+
39
+ # Enable fulltext indexing
40
+ client.execute("SET experimental_fulltext_index=1")
41
+
42
+ # Create test database and table
43
+ test_db = "sync_tx_context_test"
44
+ client.execute(f"CREATE DATABASE IF NOT EXISTS {test_db}")
45
+ client.execute(f"USE {test_db}")
46
+
47
+ client.execute("DROP TABLE IF EXISTS sync_tx_docs")
48
+ client.execute(
49
+ """
50
+ CREATE TABLE sync_tx_docs (
51
+ id INT AUTO_INCREMENT PRIMARY KEY,
52
+ title VARCHAR(255) NOT NULL,
53
+ content TEXT NOT NULL,
54
+ category VARCHAR(100) NOT NULL
55
+ )
56
+ """
57
+ )
58
+
59
+ # Insert test data
60
+ test_docs = [
61
+ ("Python Programming", "Learn Python programming basics", "Programming"),
62
+ ("JavaScript Development", "Modern JavaScript development patterns", "Programming"),
63
+ ("Database Design", "Database design principles and best practices", "Database"),
64
+ ("Web Development", "Web development with modern frameworks", "Web"),
65
+ ("Testing Strategies", "Software testing strategies and methodologies", "Testing"),
66
+ ]
67
+
68
+ for title, content, category in test_docs:
69
+ client.execute(
70
+ f"""
71
+ INSERT INTO sync_tx_docs (title, content, category)
72
+ VALUES ('{title}', '{content}', '{category}')
73
+ """
74
+ )
75
+
76
+ # Create fulltext index
77
+ client.fulltext_index.create("sync_tx_docs", "ftidx_sync_tx", ["title", "content"])
78
+
79
+ yield client, test_db
80
+
81
+ # Cleanup
82
+ try:
83
+ client.fulltext_index.drop("sync_tx_docs", "ftidx_sync_tx")
84
+ client.execute("DROP TABLE sync_tx_docs")
85
+ client.execute(f"DROP DATABASE {test_db}")
86
+ except Exception as e:
87
+ print(f"Cleanup warning: {e}")
88
+ finally:
89
+ client.disconnect()
90
+
91
+ def test_sync_client_transaction_context(self, sync_client_setup):
92
+ """Test sync client transaction context"""
93
+ client, test_db = sync_client_setup
94
+
95
+ with client.transaction() as tx:
96
+ # Test basic execute
97
+ result = tx.execute("SELECT COUNT(*) FROM sync_tx_docs")
98
+ assert result.rows[0][0] == 5
99
+
100
+ # Test fulltext search
101
+ result = tx.query(
102
+ "sync_tx_docs.title", "sync_tx_docs.content", boolean_match("title", "content").encourage("python")
103
+ ).execute()
104
+ assert len(result.rows) > 0
105
+
106
+ # Test snapshot operations (commented out as MatrixOne doesn't support snapshots in transactions)
107
+ # tx.snapshots.create("test_snap", "table", database=test_db, table="sync_tx_docs")
108
+
109
+ # Test SQLAlchemy session
110
+ session = tx.get_sqlalchemy_session()
111
+ assert session is not None
112
+
113
+ # Test get_connection for direct connection access
114
+ conn = tx.get_connection()
115
+ assert conn is not None
116
+
117
+ # Test direct connection usage
118
+ from sqlalchemy import text
119
+
120
+ result = conn.execute(text("SELECT COUNT(*) FROM sync_tx_docs"))
121
+ rows = result.fetchall()
122
+ assert rows[0][0] == 5
123
+
124
+ # Test account operations
125
+ # Note: Account operations might require special permissions
126
+ # tx.account.create_user("test_user", "test_password")
127
+
128
+ def test_sync_sqlalchemy_transaction_context(self, sync_client_setup):
129
+ """Test sync SQLAlchemy transaction context"""
130
+ client, test_db = sync_client_setup
131
+
132
+ with client.get_sqlalchemy_engine().begin() as conn:
133
+ # Test direct SQL execution
134
+ from sqlalchemy import text
135
+
136
+ result = conn.execute(text("SELECT COUNT(*) FROM sync_tx_docs"))
137
+ rows = result.fetchall()
138
+ assert rows[0][0] == 5
139
+
140
+ # Test SQLAlchemy session in transaction
141
+ from sqlalchemy.orm import sessionmaker
142
+
143
+ Session = sessionmaker(bind=conn)
144
+ session = Session()
145
+ assert session is not None
146
+ session.close()
147
+
148
+
149
+ class TestAsyncTransactionContexts:
150
+ """Test async client transaction contexts"""
151
+
152
+ @pytest_asyncio.fixture(scope="function")
153
+ async def async_client_setup(self):
154
+ """Setup async client for testing"""
155
+ client = AsyncClient()
156
+ host, port, user, password, database = online_config.get_connection_params()
157
+ await client.connect(host, port, user, password, database)
158
+
159
+ # Enable fulltext indexing
160
+ await client.execute("SET experimental_fulltext_index=1")
161
+
162
+ # Create test database and table
163
+ test_db = "async_tx_context_test"
164
+ await client.execute(f"CREATE DATABASE IF NOT EXISTS {test_db}")
165
+ await client.execute(f"USE {test_db}")
166
+
167
+ await client.execute("DROP TABLE IF EXISTS async_tx_docs")
168
+ await client.execute(
169
+ """
170
+ CREATE TABLE async_tx_docs (
171
+ id INT AUTO_INCREMENT PRIMARY KEY,
172
+ title VARCHAR(255) NOT NULL,
173
+ content TEXT NOT NULL,
174
+ category VARCHAR(100) NOT NULL
175
+ )
176
+ """
177
+ )
178
+
179
+ # Insert test data
180
+ test_docs = [
181
+ (
182
+ "Python Async Programming",
183
+ "Learn Python async programming with asyncio",
184
+ "Programming",
185
+ ),
186
+ (
187
+ "JavaScript Async Patterns",
188
+ "Modern JavaScript async patterns and promises",
189
+ "Programming",
190
+ ),
191
+ (
192
+ "Database Async Operations",
193
+ "Async database operations and connection pooling",
194
+ "Database",
195
+ ),
196
+ ("Web Async Development", "Async web development with modern frameworks", "Web"),
197
+ (
198
+ "Async Testing Strategies",
199
+ "Testing async code and handling async test cases",
200
+ "Testing",
201
+ ),
202
+ ]
203
+
204
+ for title, content, category in test_docs:
205
+ await client.execute(
206
+ f"""
207
+ INSERT INTO async_tx_docs (title, content, category)
208
+ VALUES ('{title}', '{content}', '{category}')
209
+ """
210
+ )
211
+
212
+ # Create fulltext index
213
+ await client.fulltext_index.create("async_tx_docs", "ftidx_async_tx", ["title", "content"])
214
+
215
+ yield client, test_db
216
+
217
+ # Cleanup
218
+ try:
219
+ await client.fulltext_index.drop("async_tx_docs", "ftidx_async_tx")
220
+ await client.execute("DROP TABLE async_tx_docs")
221
+ await client.execute(f"DROP DATABASE {test_db}")
222
+ except Exception as e:
223
+ print(f"Cleanup warning: {e}")
224
+ finally:
225
+ await client.disconnect()
226
+
227
+ @pytest.mark.asyncio
228
+ async def test_async_client_transaction_context(self, async_client_setup):
229
+ """Test async client transaction context"""
230
+ client, test_db = async_client_setup
231
+
232
+ async with client.transaction() as tx:
233
+ # Test basic execute
234
+ result = await tx.execute("SELECT COUNT(*) FROM async_tx_docs")
235
+ assert result.rows[0][0] == 5
236
+
237
+ # Test fulltext search
238
+ result = await tx.query(
239
+ "async_tx_docs.title", "async_tx_docs.content", boolean_match("title", "content").encourage("python")
240
+ ).execute()
241
+ assert len(result.rows) > 0
242
+
243
+ # Test snapshot operations (commented out as MatrixOne doesn't support snapshots in transactions)
244
+ # await tx.snapshots.create("test_snap", "table", database=test_db, table="async_tx_docs")
245
+
246
+ # Test SQLAlchemy session
247
+ session = await tx.get_sqlalchemy_session()
248
+ assert session is not None
249
+
250
+ # Test get_connection for direct connection access
251
+ conn = tx.get_connection()
252
+ assert conn is not None
253
+
254
+ # Test direct connection usage
255
+ from sqlalchemy import text
256
+
257
+ result = await conn.execute(text("SELECT COUNT(*) FROM async_tx_docs"))
258
+ rows = result.fetchall()
259
+ assert rows[0][0] == 5
260
+
261
+ @pytest.mark.asyncio
262
+ async def test_async_sqlalchemy_transaction_context(self, async_client_setup):
263
+ """Test async SQLAlchemy transaction context"""
264
+ client, test_db = async_client_setup
265
+
266
+ async with client.get_sqlalchemy_engine().begin() as conn:
267
+ # Test direct SQL execution
268
+ from sqlalchemy import text
269
+
270
+ result = await conn.execute(text("SELECT COUNT(*) FROM async_tx_docs"))
271
+ rows = result.fetchall()
272
+ assert rows[0][0] == 5
273
+
274
+ # Test SQLAlchemy session in transaction
275
+ try:
276
+ from sqlalchemy.ext.asyncio import AsyncSession, async_sessionmaker
277
+
278
+ AsyncSessionLocal = async_sessionmaker(bind=conn, class_=AsyncSession, expire_on_commit=False)
279
+ except ImportError:
280
+ # Fallback for older SQLAlchemy versions
281
+ from sqlalchemy.ext.asyncio import AsyncSession
282
+ from sqlalchemy.orm import sessionmaker
283
+
284
+ AsyncSessionLocal = sessionmaker(bind=conn, class_=AsyncSession, expire_on_commit=False)
285
+ session = AsyncSessionLocal()
286
+ assert session is not None
287
+ await session.close()
288
+
289
+
290
+ class TestTransactionContextCompatibility:
291
+ """Test compatibility between different transaction contexts"""
292
+
293
+ @pytest.fixture(scope="function")
294
+ def sync_client_setup(self):
295
+ """Setup sync client for testing"""
296
+ client = Client()
297
+ host, port, user, password, database = online_config.get_connection_params()
298
+ client.connect(host, port, user, password, database)
299
+
300
+ # Enable fulltext indexing
301
+ client.execute("SET experimental_fulltext_index=1")
302
+
303
+ # Create test database and table
304
+ test_db = "compat_tx_test"
305
+ client.execute(f"CREATE DATABASE IF NOT EXISTS {test_db}")
306
+ client.execute(f"USE {test_db}")
307
+
308
+ client.execute("DROP TABLE IF EXISTS compat_tx_docs")
309
+ client.execute(
310
+ """
311
+ CREATE TABLE compat_tx_docs (
312
+ id INT AUTO_INCREMENT PRIMARY KEY,
313
+ title VARCHAR(255) NOT NULL,
314
+ content TEXT NOT NULL
315
+ )
316
+ """
317
+ )
318
+
319
+ # Insert test data
320
+ client.execute("INSERT INTO compat_tx_docs (title, content) VALUES ('Test Title', 'Test Content')")
321
+
322
+ # Create fulltext index
323
+ client.fulltext_index.create("compat_tx_docs", "ftidx_compat", ["title", "content"])
324
+
325
+ yield client, test_db
326
+
327
+ # Cleanup
328
+ try:
329
+ client.fulltext_index.drop("compat_tx_docs", "ftidx_compat")
330
+ client.execute("DROP TABLE compat_tx_docs")
331
+ client.execute(f"DROP DATABASE {test_db}")
332
+ except Exception as e:
333
+ print(f"Cleanup warning: {e}")
334
+ finally:
335
+ client.disconnect()
336
+
337
+ def test_sync_transaction_wrapper_has_all_managers(self, sync_client_setup):
338
+ """Test that sync transaction wrapper has all necessary managers"""
339
+ client, test_db = sync_client_setup
340
+
341
+ with client.transaction() as tx:
342
+ # Check that all managers are available
343
+ assert hasattr(tx, 'execute')
344
+ assert hasattr(tx, 'snapshots')
345
+ assert hasattr(tx, 'clone')
346
+ assert hasattr(tx, 'restore')
347
+ assert hasattr(tx, 'pitr')
348
+ assert hasattr(tx, 'pubsub')
349
+ assert hasattr(tx, 'account')
350
+ assert hasattr(tx, 'vector_ops')
351
+ assert hasattr(tx, 'fulltext_index')
352
+ assert hasattr(tx, 'get_sqlalchemy_session')
353
+
354
+ # Test that fulltext_index has create and drop methods
355
+ assert hasattr(tx.fulltext_index, 'create')
356
+ assert hasattr(tx.fulltext_index, 'drop')
357
+
358
+ # Test that query method works in transaction context
359
+ result = tx.query("compat_tx_docs.title", "compat_tx_docs.content").execute()
360
+ assert len(result.rows) > 0
361
+
362
+ @pytest.mark.asyncio
363
+ async def test_async_transaction_wrapper_has_all_managers(self):
364
+ """Test that async transaction wrapper has all necessary managers"""
365
+ client = AsyncClient()
366
+ host, port, user, password, database = online_config.get_connection_params()
367
+ await client.connect(host, port, user, password, database)
368
+
369
+ try:
370
+ # Enable fulltext indexing
371
+ await client.execute("SET experimental_fulltext_index=1")
372
+
373
+ # Create test database and table
374
+ test_db = "async_compat_tx_test"
375
+ await client.execute(f"CREATE DATABASE IF NOT EXISTS {test_db}")
376
+ await client.execute(f"USE {test_db}")
377
+
378
+ await client.execute("DROP TABLE IF EXISTS async_compat_tx_docs")
379
+ await client.execute(
380
+ """
381
+ CREATE TABLE async_compat_tx_docs (
382
+ id INT AUTO_INCREMENT PRIMARY KEY,
383
+ title VARCHAR(255) NOT NULL,
384
+ content TEXT NOT NULL
385
+ )
386
+ """
387
+ )
388
+
389
+ # Insert test data
390
+ await client.execute("INSERT INTO async_compat_tx_docs (title, content) VALUES ('Test Title', 'Test Content')")
391
+
392
+ # Create fulltext index
393
+ await client.fulltext_index.create("async_compat_tx_docs", "ftidx_async_compat", ["title", "content"])
394
+
395
+ async with client.transaction() as tx:
396
+ # Check that all managers are available
397
+ assert hasattr(tx, 'execute')
398
+ assert hasattr(tx, 'snapshots')
399
+ assert hasattr(tx, 'clone')
400
+ assert hasattr(tx, 'restore')
401
+ assert hasattr(tx, 'pitr')
402
+ assert hasattr(tx, 'pubsub')
403
+ assert hasattr(tx, 'account')
404
+ assert hasattr(tx, 'fulltext_index')
405
+ assert hasattr(tx, 'get_sqlalchemy_session')
406
+
407
+ # Test that fulltext_index has create and drop methods
408
+ assert hasattr(tx.fulltext_index, 'create')
409
+ assert hasattr(tx.fulltext_index, 'drop')
410
+
411
+ # Test that query method works in transaction context
412
+ result = await tx.query("async_compat_tx_docs.title", "async_compat_tx_docs.content").execute()
413
+ assert len(result.rows) > 0
414
+
415
+ finally:
416
+ # Cleanup
417
+ try:
418
+ await client.fulltext_index.drop("async_compat_tx_docs", "ftidx_async_compat")
419
+ await client.execute("DROP TABLE async_compat_tx_docs")
420
+ await client.execute(f"DROP DATABASE {test_db}")
421
+ except Exception as e:
422
+ print(f"Cleanup warning: {e}")
423
+ finally:
424
+ await client.disconnect()
425
+
426
+
427
+ class TestTransactionContextFeatures:
428
+ """Test specific features in transaction contexts"""
429
+
430
+ @pytest.fixture(scope="function")
431
+ def sync_client_setup(self):
432
+ """Setup sync client for testing"""
433
+ client = Client()
434
+ host, port, user, password, database = online_config.get_connection_params()
435
+ client.connect(host, port, user, password, database)
436
+
437
+ # Enable fulltext indexing
438
+ client.execute("SET experimental_fulltext_index=1")
439
+
440
+ # Create test database and table
441
+ test_db = "features_tx_test"
442
+ client.execute(f"CREATE DATABASE IF NOT EXISTS {test_db}")
443
+ client.execute(f"USE {test_db}")
444
+
445
+ client.execute("DROP TABLE IF EXISTS features_tx_docs")
446
+ client.execute(
447
+ """
448
+ CREATE TABLE features_tx_docs (
449
+ id INT AUTO_INCREMENT PRIMARY KEY,
450
+ title VARCHAR(255) NOT NULL,
451
+ content TEXT NOT NULL,
452
+ category VARCHAR(100) NOT NULL
453
+ )
454
+ """
455
+ )
456
+
457
+ # Insert test data
458
+ test_docs = [
459
+ ("Feature Test 1", "Content for feature test 1", "Category1"),
460
+ ("Feature Test 2", "Content for feature test 2", "Category2"),
461
+ ("Feature Test 3", "Content for feature test 3", "Category1"),
462
+ ]
463
+
464
+ for title, content, category in test_docs:
465
+ client.execute(
466
+ f"""
467
+ INSERT INTO features_tx_docs (title, content, category)
468
+ VALUES ('{title}', '{content}', '{category}')
469
+ """
470
+ )
471
+
472
+ # Create fulltext index
473
+ client.fulltext_index.create("features_tx_docs", "ftidx_features", ["title", "content"])
474
+
475
+ yield client, test_db
476
+
477
+ # Cleanup
478
+ try:
479
+ client.fulltext_index.drop("features_tx_docs", "ftidx_features")
480
+ client.execute("DROP TABLE features_tx_docs")
481
+ client.execute(f"DROP DATABASE {test_db}")
482
+ except Exception as e:
483
+ print(f"Cleanup warning: {e}")
484
+ finally:
485
+ client.disconnect()
486
+
487
+ def test_sync_fulltext_features_in_transaction(self, sync_client_setup):
488
+ """Test fulltext features in sync transaction context"""
489
+ client, test_db = sync_client_setup
490
+
491
+ with client.transaction() as tx:
492
+ # Test basic search
493
+ result = tx.query(
494
+ "features_tx_docs.title", "features_tx_docs.content", boolean_match("title", "content").encourage("feature")
495
+ ).execute()
496
+ assert len(result.rows) > 0
497
+
498
+ # Test with score
499
+ result = tx.query(
500
+ "features_tx_docs.title",
501
+ "features_tx_docs.content",
502
+ boolean_match("title", "content").encourage("test").label("score"),
503
+ ).execute()
504
+ assert len(result.rows) > 0
505
+ # Check that score column is present
506
+ assert len(result.columns) > 2 # title, content, score
507
+
508
+ # Test boolean mode
509
+ result = tx.query(
510
+ "features_tx_docs.title", "features_tx_docs.content", boolean_match("title", "content").must("feature")
511
+ ).execute()
512
+ assert len(result.rows) > 0
513
+
514
+ # Test with WHERE conditions
515
+ result = (
516
+ tx.query("features_tx_docs.title", "features_tx_docs.content")
517
+ .filter(boolean_match("title", "content").encourage("test"), "features_tx_docs.category = 'Category1'")
518
+ .execute()
519
+ )
520
+ assert len(result.rows) > 0
521
+
522
+ # Test ordering and limit
523
+ result = (
524
+ tx.query(
525
+ "features_tx_docs.title",
526
+ "features_tx_docs.content",
527
+ boolean_match("title", "content").encourage("test").label("score"),
528
+ )
529
+ .order_by("score ASC")
530
+ .limit(2)
531
+ .execute()
532
+ )
533
+ assert len(result.rows) <= 2
534
+
535
+ @pytest.mark.asyncio
536
+ async def test_async_fulltext_features_in_transaction(self):
537
+ """Test fulltext features in async transaction context"""
538
+ client = AsyncClient()
539
+ host, port, user, password, database = online_config.get_connection_params()
540
+ await client.connect(host, port, user, password, database)
541
+
542
+ try:
543
+ # Enable fulltext indexing
544
+ await client.execute("SET experimental_fulltext_index=1")
545
+
546
+ # Create test database and table
547
+ test_db = "async_features_tx_test"
548
+ await client.execute(f"CREATE DATABASE IF NOT EXISTS {test_db}")
549
+ await client.execute(f"USE {test_db}")
550
+
551
+ await client.execute("DROP TABLE IF EXISTS async_features_tx_docs")
552
+ await client.execute(
553
+ """
554
+ CREATE TABLE async_features_tx_docs (
555
+ id INT AUTO_INCREMENT PRIMARY KEY,
556
+ title VARCHAR(255) NOT NULL,
557
+ content TEXT NOT NULL,
558
+ category VARCHAR(100) NOT NULL
559
+ )
560
+ """
561
+ )
562
+
563
+ # Insert test data
564
+ test_docs = [
565
+ ("Async Feature Test 1", "Content for async feature test 1", "Category1"),
566
+ ("Async Feature Test 2", "Content for async feature test 2", "Category2"),
567
+ ("Async Feature Test 3", "Content for async feature test 3", "Category1"),
568
+ ]
569
+
570
+ for title, content, category in test_docs:
571
+ await client.execute(
572
+ f"""
573
+ INSERT INTO async_features_tx_docs (title, content, category)
574
+ VALUES ('{title}', '{content}', '{category}')
575
+ """
576
+ )
577
+
578
+ # Create fulltext index
579
+ await client.fulltext_index.create("async_features_tx_docs", "ftidx_async_features", ["title", "content"])
580
+
581
+ async with client.transaction() as tx:
582
+ # Test basic search
583
+ result = await tx.query(
584
+ "async_features_tx_docs.title",
585
+ "async_features_tx_docs.content",
586
+ boolean_match("title", "content").encourage("async"),
587
+ ).execute()
588
+ assert len(result.rows) > 0
589
+
590
+ # Test with score
591
+ result = await tx.query(
592
+ "async_features_tx_docs.title",
593
+ "async_features_tx_docs.content",
594
+ boolean_match("title", "content").encourage("test").label("score"),
595
+ ).execute()
596
+ assert len(result.rows) > 0
597
+ # Check that score column is present
598
+ assert len(result.columns) > 2 # title, content, score
599
+
600
+ # Test boolean mode
601
+ result = await tx.query(
602
+ "async_features_tx_docs.title",
603
+ "async_features_tx_docs.content",
604
+ boolean_match("title", "content").must("async"),
605
+ ).execute()
606
+ assert len(result.rows) > 0
607
+
608
+ # Test with WHERE conditions
609
+ result = (
610
+ await tx.query("async_features_tx_docs.title", "async_features_tx_docs.content")
611
+ .filter(
612
+ boolean_match("title", "content").encourage("test"), "async_features_tx_docs.category = 'Category1'"
613
+ )
614
+ .execute()
615
+ )
616
+ assert len(result.rows) > 0
617
+
618
+ # Test ordering and limit
619
+ result = (
620
+ await tx.query(
621
+ "async_features_tx_docs.title",
622
+ "async_features_tx_docs.content",
623
+ boolean_match("title", "content").encourage("test").label("score"),
624
+ )
625
+ .order_by("score ASC")
626
+ .limit(2)
627
+ .execute()
628
+ )
629
+ assert len(result.rows) <= 2
630
+
631
+ finally:
632
+ # Cleanup
633
+ try:
634
+ await client.fulltext_index.drop("async_features_tx_docs", "ftidx_async_features")
635
+ await client.execute("DROP TABLE async_features_tx_docs")
636
+ await client.execute(f"DROP DATABASE {test_db}")
637
+ except Exception as e:
638
+ print(f"Cleanup warning: {e}")
639
+ finally:
640
+ await client.disconnect()
641
+
642
+
643
+ class TestGetConnectionInterface:
644
+ """Test get_connection interface in transaction contexts"""
645
+
646
+ @pytest.fixture(scope="function")
647
+ def sync_client_setup(self):
648
+ """Setup sync client for testing"""
649
+ client = Client()
650
+ host, port, user, password, database = online_config.get_connection_params()
651
+ client.connect(host, port, user, password, database)
652
+
653
+ # Create test database and table
654
+ test_db = "get_conn_test"
655
+ client.execute(f"CREATE DATABASE IF NOT EXISTS {test_db}")
656
+ client.execute(f"USE {test_db}")
657
+
658
+ client.execute("DROP TABLE IF EXISTS get_conn_docs")
659
+ client.execute(
660
+ """
661
+ CREATE TABLE get_conn_docs (
662
+ id INT AUTO_INCREMENT PRIMARY KEY,
663
+ title VARCHAR(255) NOT NULL,
664
+ content TEXT NOT NULL,
665
+ created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
666
+ )
667
+ """
668
+ )
669
+
670
+ # Insert test data
671
+ test_docs = [
672
+ ("Connection Test 1", "Content for connection test 1"),
673
+ ("Connection Test 2", "Content for connection test 2"),
674
+ ("Connection Test 3", "Content for connection test 3"),
675
+ ]
676
+
677
+ for title, content in test_docs:
678
+ client.execute(
679
+ f"""
680
+ INSERT INTO get_conn_docs (title, content)
681
+ VALUES ('{title}', '{content}')
682
+ """
683
+ )
684
+
685
+ yield client, test_db
686
+
687
+ # Cleanup
688
+ try:
689
+ client.execute("DROP TABLE get_conn_docs")
690
+ client.execute(f"DROP DATABASE {test_db}")
691
+ except Exception as e:
692
+ print(f"Cleanup warning: {e}")
693
+ finally:
694
+ client.disconnect()
695
+
696
+ def test_sync_get_connection_basic_usage(self, sync_client_setup):
697
+ """Test basic usage of get_connection in sync transaction"""
698
+ client, test_db = sync_client_setup
699
+
700
+ with client.transaction() as tx:
701
+ # Get connection
702
+ conn = tx.get_connection()
703
+ assert conn is not None
704
+
705
+ # Test basic query execution
706
+ from sqlalchemy import text
707
+
708
+ result = conn.execute(text(f"SELECT COUNT(*) FROM {test_db}.get_conn_docs"))
709
+ rows = result.fetchall()
710
+ assert rows[0][0] == 3
711
+
712
+ # Test parameterized query
713
+ result = conn.execute(
714
+ text(f"SELECT * FROM {test_db}.get_conn_docs WHERE title LIKE :pattern"),
715
+ {"pattern": "%Test 1%"},
716
+ )
717
+ rows = result.fetchall()
718
+ assert len(rows) == 1
719
+ assert "Test 1" in rows[0][1] # title column
720
+
721
+ def test_sync_get_connection_transaction_isolation(self, sync_client_setup):
722
+ """Test that get_connection maintains transaction isolation"""
723
+ client, test_db = sync_client_setup
724
+
725
+ # First, verify initial state
726
+ result = client.execute(f"SELECT COUNT(*) FROM {test_db}.get_conn_docs")
727
+ initial_count = result.rows[0][0]
728
+
729
+ with client.transaction() as tx:
730
+ conn = tx.get_connection()
731
+
732
+ # Insert a new record within transaction
733
+ from sqlalchemy import text
734
+
735
+ conn.execute(
736
+ text(
737
+ f"INSERT INTO {test_db}.get_conn_docs (title, content) VALUES ('Transaction Test', 'Content in transaction')"
738
+ )
739
+ )
740
+
741
+ # Verify record exists within transaction
742
+ result = conn.execute(text(f"SELECT COUNT(*) FROM {test_db}.get_conn_docs"))
743
+ count_in_tx = result.scalar()
744
+ assert count_in_tx == initial_count + 1
745
+
746
+ # Verify record doesn't exist outside transaction (from another connection)
747
+ result = client.execute(f"SELECT COUNT(*) FROM {test_db}.get_conn_docs")
748
+ count_outside = result.rows[0][0]
749
+ assert count_outside == initial_count # Should still be original count
750
+
751
+ # After transaction commit, verify record exists
752
+ result = client.execute(f"SELECT COUNT(*) FROM {test_db}.get_conn_docs")
753
+ final_count = result.rows[0][0]
754
+ assert final_count == initial_count + 1
755
+
756
+ @pytest.mark.asyncio
757
+ async def test_async_get_connection_basic_usage(self):
758
+ """Test basic usage of get_connection in async transaction"""
759
+ client = AsyncClient()
760
+ host, port, user, password, database = online_config.get_connection_params()
761
+ await client.connect(host, port, user, password, database)
762
+
763
+ try:
764
+ # Create test database and table
765
+ test_db = "async_get_conn_test"
766
+ await client.execute(f"CREATE DATABASE IF NOT EXISTS {test_db}")
767
+ await client.execute(f"USE {test_db}")
768
+
769
+ await client.execute("DROP TABLE IF EXISTS async_get_conn_docs")
770
+ await client.execute(
771
+ """
772
+ CREATE TABLE async_get_conn_docs (
773
+ id INT AUTO_INCREMENT PRIMARY KEY,
774
+ title VARCHAR(255) NOT NULL,
775
+ content TEXT NOT NULL,
776
+ created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
777
+ )
778
+ """
779
+ )
780
+
781
+ # Insert test data
782
+ test_docs = [
783
+ ("Async Connection Test 1", "Content for async connection test 1"),
784
+ ("Async Connection Test 2", "Content for async connection test 2"),
785
+ ("Async Connection Test 3", "Content for async connection test 3"),
786
+ ]
787
+
788
+ for title, content in test_docs:
789
+ await client.execute(
790
+ f"""
791
+ INSERT INTO async_get_conn_docs (title, content)
792
+ VALUES ('{title}', '{content}')
793
+ """
794
+ )
795
+
796
+ async with client.transaction() as tx:
797
+ # Get connection
798
+ conn = tx.get_connection()
799
+ assert conn is not None
800
+
801
+ # Test basic query execution
802
+ from sqlalchemy import text
803
+
804
+ result = await conn.execute(text("SELECT COUNT(*) FROM async_get_conn_docs"))
805
+ rows = result.fetchall()
806
+ assert rows[0][0] == 3
807
+
808
+ # Test parameterized query
809
+ result = await conn.execute(
810
+ text("SELECT * FROM async_get_conn_docs WHERE title LIKE :pattern"),
811
+ {"pattern": "%Test 1%"},
812
+ )
813
+ rows = result.fetchall()
814
+ assert len(rows) == 1
815
+ assert "Test 1" in rows[0][1] # title column
816
+
817
+ finally:
818
+ # Cleanup
819
+ try:
820
+ await client.execute("DROP TABLE async_get_conn_docs")
821
+ await client.execute(f"DROP DATABASE {test_db}")
822
+ except Exception as e:
823
+ print(f"Cleanup warning: {e}")
824
+ finally:
825
+ await client.disconnect()
826
+
827
+ def test_sync_get_connection_with_other_managers(self, sync_client_setup):
828
+ """Test that get_connection works alongside other transaction managers"""
829
+ client, test_db = sync_client_setup
830
+
831
+ with client.transaction() as tx:
832
+ # Test that we can use both get_connection and other managers
833
+ conn = tx.get_connection()
834
+
835
+ # Use connection directly
836
+ from sqlalchemy import text
837
+
838
+ result = conn.execute(text(f"SELECT COUNT(*) FROM {test_db}.get_conn_docs"))
839
+ direct_count = result.scalar()
840
+
841
+ # Use transaction wrapper execute
842
+ result = tx.execute(f"SELECT COUNT(*) FROM {test_db}.get_conn_docs")
843
+ wrapper_count = result.rows[0][0]
844
+
845
+ # Both should return the same result
846
+ assert direct_count == wrapper_count
847
+
848
+ # Test that both are in the same transaction
849
+ conn.execute(
850
+ text(
851
+ f"INSERT INTO {test_db}.get_conn_docs (title, content) VALUES ('Direct Insert', 'Using connection directly')"
852
+ )
853
+ )
854
+ tx.execute(
855
+ f"INSERT INTO {test_db}.get_conn_docs (title, content) VALUES ('Wrapper Insert', 'Using transaction wrapper')"
856
+ )
857
+
858
+ # Both inserts should be visible within the transaction
859
+ result = conn.execute(text(f"SELECT COUNT(*) FROM {test_db}.get_conn_docs"))
860
+ final_count = result.scalar()
861
+ assert final_count == direct_count + 2
862
+
863
+
864
+ class TestAsyncTransactionManagerConsistency:
865
+ """Test that async transaction managers behave consistently with sync versions"""
866
+
867
+ @pytest.fixture(scope="function")
868
+ def sync_client_setup(self):
869
+ """Setup sync client for testing"""
870
+ client = Client()
871
+ host, port, user, password, database = online_config.get_connection_params()
872
+ client.connect(host, port, user, password, database)
873
+
874
+ # Create test database and table
875
+ test_db = "sync_async_consistency_test"
876
+ client.execute(f"CREATE DATABASE IF NOT EXISTS {test_db}")
877
+ client.execute(f"USE {test_db}")
878
+
879
+ client.execute("DROP TABLE IF EXISTS consistency_docs")
880
+ client.execute(
881
+ """
882
+ CREATE TABLE consistency_docs (
883
+ id INT AUTO_INCREMENT PRIMARY KEY,
884
+ title VARCHAR(255) NOT NULL,
885
+ content TEXT NOT NULL,
886
+ category VARCHAR(100) NOT NULL,
887
+ priority INT DEFAULT 1
888
+ )
889
+ """
890
+ )
891
+
892
+ # Insert test data
893
+ test_docs = [
894
+ ("Sync Test 1", "Content for sync test 1", "Category1", 1),
895
+ ("Sync Test 2", "Content for sync test 2", "Category2", 2),
896
+ ("Sync Test 3", "Content for sync test 3", "Category1", 3),
897
+ ]
898
+
899
+ for title, content, category, priority in test_docs:
900
+ client.execute(
901
+ f"""
902
+ INSERT INTO consistency_docs (title, content, category, priority)
903
+ VALUES ('{title}', '{content}', '{category}', {priority})
904
+ """
905
+ )
906
+
907
+ yield client, test_db
908
+
909
+ # Cleanup
910
+ try:
911
+ client.execute("DROP TABLE consistency_docs")
912
+ client.execute(f"DROP DATABASE {test_db}")
913
+ except Exception as e:
914
+ print(f"Cleanup warning: {e}")
915
+ finally:
916
+ client.disconnect()
917
+
918
+ @pytest_asyncio.fixture(scope="function")
919
+ async def async_client_setup(self):
920
+ """Setup async client for testing"""
921
+ client = AsyncClient()
922
+ host, port, user, password, database = online_config.get_connection_params()
923
+ await client.connect(host, port, user, password, database)
924
+
925
+ # Create test database and table
926
+ test_db = "async_consistency_test"
927
+ await client.execute(f"CREATE DATABASE IF NOT EXISTS {test_db}")
928
+ await client.execute(f"USE {test_db}")
929
+
930
+ await client.execute("DROP TABLE IF EXISTS async_consistency_docs")
931
+ await client.execute(
932
+ """
933
+ CREATE TABLE async_consistency_docs (
934
+ id INT AUTO_INCREMENT PRIMARY KEY,
935
+ title VARCHAR(255) NOT NULL,
936
+ content TEXT NOT NULL,
937
+ category VARCHAR(100) NOT NULL,
938
+ priority INT DEFAULT 1
939
+ )
940
+ """
941
+ )
942
+
943
+ # Insert test data
944
+ test_docs = [
945
+ ("Async Test 1", "Content for async test 1", "Category1", 1),
946
+ ("Async Test 2", "Content for async test 2", "Category2", 2),
947
+ ("Async Test 3", "Content for async test 3", "Category1", 3),
948
+ ]
949
+
950
+ for title, content, category, priority in test_docs:
951
+ await client.execute(
952
+ f"""
953
+ INSERT INTO async_consistency_docs (title, content, category, priority)
954
+ VALUES ('{title}', '{content}', '{category}', {priority})
955
+ """
956
+ )
957
+
958
+ yield client, test_db
959
+
960
+ # Cleanup
961
+ try:
962
+ await client.execute("DROP TABLE async_consistency_docs")
963
+ await client.execute(f"DROP DATABASE {test_db}")
964
+ except Exception as e:
965
+ print(f"Cleanup warning: {e}")
966
+ finally:
967
+ await client.disconnect()
968
+
969
+ def test_sync_transaction_managers_availability(self, sync_client_setup):
970
+ """Test that all expected transaction managers are available in sync client"""
971
+ client, test_db = sync_client_setup
972
+
973
+ with client.transaction() as tx:
974
+ # Test that all expected managers are available
975
+ assert hasattr(tx, 'snapshots')
976
+ assert hasattr(tx, 'clone')
977
+ assert hasattr(tx, 'restore')
978
+ assert hasattr(tx, 'pitr')
979
+ assert hasattr(tx, 'pubsub')
980
+ assert hasattr(tx, 'account')
981
+ assert hasattr(tx, 'vector_ops')
982
+ assert hasattr(tx, 'fulltext_index')
983
+ assert hasattr(tx, 'get_connection')
984
+ assert hasattr(tx, 'get_sqlalchemy_session')
985
+
986
+ # Test that managers are the correct types
987
+ from matrixone.client import (
988
+ TransactionSnapshotManager,
989
+ TransactionCloneManager,
990
+ TransactionRestoreManager,
991
+ TransactionPitrManager,
992
+ TransactionPubSubManager,
993
+ TransactionAccountManager,
994
+ TransactionVectorIndexManager,
995
+ TransactionFulltextIndexManager,
996
+ )
997
+
998
+ assert isinstance(tx.snapshots, TransactionSnapshotManager)
999
+ assert isinstance(tx.clone, TransactionCloneManager)
1000
+ assert isinstance(tx.restore, TransactionRestoreManager)
1001
+ assert isinstance(tx.pitr, TransactionPitrManager)
1002
+ assert isinstance(tx.pubsub, TransactionPubSubManager)
1003
+ assert isinstance(tx.account, TransactionAccountManager)
1004
+ assert isinstance(tx.vector_ops, TransactionVectorIndexManager)
1005
+ assert isinstance(tx.fulltext_index, TransactionFulltextIndexManager)
1006
+
1007
+ @pytest.mark.asyncio
1008
+ async def test_async_transaction_managers_availability(self, async_client_setup):
1009
+ """Test that all expected transaction managers are available in async client"""
1010
+ client, test_db = async_client_setup
1011
+
1012
+ async with client.transaction() as tx:
1013
+ # Test that all expected managers are available
1014
+ assert hasattr(tx, 'snapshots')
1015
+ assert hasattr(tx, 'clone')
1016
+ assert hasattr(tx, 'restore')
1017
+ assert hasattr(tx, 'pitr')
1018
+ assert hasattr(tx, 'pubsub')
1019
+ assert hasattr(tx, 'account')
1020
+ assert hasattr(tx, 'vector_ops')
1021
+ assert hasattr(tx, 'fulltext_index')
1022
+ assert hasattr(tx, 'get_connection')
1023
+ assert hasattr(tx, 'get_sqlalchemy_session')
1024
+
1025
+ # Test that managers are the correct types
1026
+ from matrixone.async_client import (
1027
+ AsyncTransactionSnapshotManager,
1028
+ AsyncTransactionCloneManager,
1029
+ AsyncTransactionRestoreManager,
1030
+ AsyncTransactionPitrManager,
1031
+ AsyncTransactionPubSubManager,
1032
+ AsyncTransactionAccountManager,
1033
+ AsyncTransactionVectorIndexManager,
1034
+ AsyncTransactionFulltextIndexManager,
1035
+ )
1036
+
1037
+ assert isinstance(tx.snapshots, AsyncTransactionSnapshotManager)
1038
+ assert isinstance(tx.clone, AsyncTransactionCloneManager)
1039
+ assert isinstance(tx.restore, AsyncTransactionRestoreManager)
1040
+ assert isinstance(tx.pitr, AsyncTransactionPitrManager)
1041
+ assert isinstance(tx.pubsub, AsyncTransactionPubSubManager)
1042
+ assert isinstance(tx.account, AsyncTransactionAccountManager)
1043
+ assert isinstance(tx.vector_ops, AsyncTransactionVectorIndexManager)
1044
+ assert isinstance(tx.fulltext_index, AsyncTransactionFulltextIndexManager)
1045
+
1046
+ def test_sync_vector_ops_transaction_behavior(self, sync_client_setup):
1047
+ """Test sync vector_ops manager behavior in transaction"""
1048
+ client, test_db = sync_client_setup
1049
+
1050
+ with client.transaction() as tx:
1051
+ # Test that vector_ops manager has execute method
1052
+ assert hasattr(tx.vector_ops, 'execute')
1053
+
1054
+ # Test basic query execution through vector_ops
1055
+ result = tx.vector_ops.execute(f"SELECT COUNT(*) FROM {test_db}.consistency_docs")
1056
+ assert result.rows[0][0] == 3
1057
+
1058
+ # Test that vector_ops uses the same transaction
1059
+ tx.vector_ops.execute(
1060
+ f"INSERT INTO {test_db}.consistency_docs (title, content, category, priority) VALUES ('Vector Ops Test', 'Content from vector ops', 'Vector', 1)"
1061
+ )
1062
+
1063
+ # Verify the insert is visible within the transaction
1064
+ result = tx.vector_ops.execute(f"SELECT COUNT(*) FROM {test_db}.consistency_docs")
1065
+ assert result.rows[0][0] == 4
1066
+
1067
+ @pytest.mark.asyncio
1068
+ async def test_async_vector_ops_transaction_behavior(self, async_client_setup):
1069
+ """Test async vector_ops manager behavior in transaction"""
1070
+ client, test_db = async_client_setup
1071
+
1072
+ async with client.transaction() as tx:
1073
+ # Test that vector_ops manager has execute method
1074
+ assert hasattr(tx.vector_ops, 'execute')
1075
+
1076
+ # Test basic query execution through vector_ops
1077
+ result = await tx.vector_ops.execute(f"SELECT COUNT(*) FROM {test_db}.async_consistency_docs")
1078
+ assert result.rows[0][0] == 3
1079
+
1080
+ # Test that vector_ops uses the same transaction
1081
+ await tx.vector_ops.execute(
1082
+ f"INSERT INTO {test_db}.async_consistency_docs (title, content, category, priority) VALUES ('Async Vector Ops Test', 'Content from async vector ops', 'Vector', 1)"
1083
+ )
1084
+
1085
+ # Verify the insert is visible within the transaction
1086
+ result = await tx.vector_ops.execute(f"SELECT COUNT(*) FROM {test_db}.async_consistency_docs")
1087
+ assert result.rows[0][0] == 4
1088
+
1089
+ def test_sync_transaction_isolation_consistency(self, sync_client_setup):
1090
+ """Test that sync transaction managers maintain proper isolation"""
1091
+ client, test_db = sync_client_setup
1092
+
1093
+ # Get initial count
1094
+ result = client.execute(f"SELECT COUNT(*) FROM {test_db}.consistency_docs")
1095
+ initial_count = result.rows[0][0]
1096
+
1097
+ with client.transaction() as tx:
1098
+ # Use different managers to perform operations
1099
+ tx.vector_ops.execute(
1100
+ f"INSERT INTO {test_db}.consistency_docs (title, content, category, priority) VALUES ('Vector Ops Insert', 'Content', 'Test', 1)"
1101
+ )
1102
+ tx.execute(
1103
+ f"INSERT INTO {test_db}.consistency_docs (title, content, category, priority) VALUES ('Direct Insert', 'Content', 'Test', 1)"
1104
+ )
1105
+
1106
+ # All managers should see the same data within transaction
1107
+ result1 = tx.vector_ops.execute(f"SELECT COUNT(*) FROM {test_db}.consistency_docs")
1108
+ result2 = tx.execute(f"SELECT COUNT(*) FROM {test_db}.consistency_docs")
1109
+
1110
+ expected_count = initial_count + 2
1111
+ assert result1.rows[0][0] == expected_count
1112
+ assert result2.rows[0][0] == expected_count
1113
+
1114
+ # Outside transaction should not see the changes yet
1115
+ result_outside = client.execute(f"SELECT COUNT(*) FROM {test_db}.consistency_docs")
1116
+ assert result_outside.rows[0][0] == initial_count
1117
+
1118
+ # After transaction commit, all changes should be visible
1119
+ result_final = client.execute(f"SELECT COUNT(*) FROM {test_db}.consistency_docs")
1120
+ assert result_final.rows[0][0] == initial_count + 2
1121
+
1122
+ @pytest.mark.asyncio
1123
+ async def test_async_transaction_isolation_consistency(self, async_client_setup):
1124
+ """Test that async transaction managers maintain proper isolation"""
1125
+ client, test_db = async_client_setup
1126
+
1127
+ # Get initial count
1128
+ result = await client.execute(f"SELECT COUNT(*) FROM {test_db}.async_consistency_docs")
1129
+ initial_count = result.rows[0][0]
1130
+
1131
+ async with client.transaction() as tx:
1132
+ # Use different managers to perform operations
1133
+ await tx.vector_ops.execute(
1134
+ f"INSERT INTO {test_db}.async_consistency_docs (title, content, category, priority) VALUES ('Async Vector Ops Insert', 'Content', 'Test', 1)"
1135
+ )
1136
+ await tx.execute(
1137
+ f"INSERT INTO {test_db}.async_consistency_docs (title, content, category, priority) VALUES ('Async Direct Insert', 'Content', 'Test', 1)"
1138
+ )
1139
+
1140
+ # All managers should see the same data within transaction
1141
+ result1 = await tx.vector_ops.execute(f"SELECT COUNT(*) FROM {test_db}.async_consistency_docs")
1142
+ result2 = await tx.execute(f"SELECT COUNT(*) FROM {test_db}.async_consistency_docs")
1143
+
1144
+ expected_count = initial_count + 2
1145
+ assert result1.rows[0][0] == expected_count
1146
+ assert result2.rows[0][0] == expected_count
1147
+
1148
+ # Outside transaction should not see the changes yet
1149
+ result_outside = await client.execute(f"SELECT COUNT(*) FROM {test_db}.async_consistency_docs")
1150
+ assert result_outside.rows[0][0] == initial_count
1151
+
1152
+ # After transaction commit, all changes should be visible
1153
+ result_final = await client.execute(f"SELECT COUNT(*) FROM {test_db}.async_consistency_docs")
1154
+ assert result_final.rows[0][0] == initial_count + 2
1155
+
1156
+ def test_sync_async_behavior_consistency(self, sync_client_setup):
1157
+ """Test that sync and async transaction managers behave consistently"""
1158
+ client, test_db = sync_client_setup
1159
+
1160
+ # Get initial count first
1161
+ result = client.execute(f"SELECT COUNT(*) FROM {test_db}.consistency_docs")
1162
+ initial_count = result.rows[0][0]
1163
+
1164
+ with client.transaction() as tx:
1165
+ # Test that all managers can execute queries
1166
+ managers_to_test = [
1167
+ ('vector_ops', tx.vector_ops),
1168
+ ('direct', tx),
1169
+ ]
1170
+
1171
+ for i, (manager_name, manager) in enumerate(managers_to_test):
1172
+ # Check current count (should be initial + number of previous inserts)
1173
+ expected_count = initial_count + i
1174
+ result = manager.execute(f"SELECT COUNT(*) FROM {test_db}.consistency_docs")
1175
+ assert (
1176
+ result.rows[0][0] == expected_count
1177
+ ), f"Manager {manager_name} returned unexpected count {result.rows[0][0]}, expected {expected_count}"
1178
+
1179
+ # Test insert through each manager
1180
+ manager.execute(
1181
+ f"INSERT INTO {test_db}.consistency_docs (title, content, category, priority) VALUES ('{manager_name} Test', 'Content', 'Test', 1)"
1182
+ )
1183
+
1184
+ # Verify insert is visible
1185
+ result = manager.execute(f"SELECT COUNT(*) FROM {test_db}.consistency_docs")
1186
+ assert result.rows[0][0] == expected_count + 1, f"Manager {manager_name} did not see its own insert"
1187
+
1188
+ @pytest.mark.asyncio
1189
+ async def test_async_async_behavior_consistency(self, async_client_setup):
1190
+ """Test that async transaction managers behave consistently"""
1191
+ client, test_db = async_client_setup
1192
+
1193
+ # Get initial count first
1194
+ result = await client.execute(f"SELECT COUNT(*) FROM {test_db}.async_consistency_docs")
1195
+ initial_count = result.rows[0][0]
1196
+
1197
+ async with client.transaction() as tx:
1198
+ # Test that all managers can execute queries
1199
+ managers_to_test = [
1200
+ ('vector_ops', tx.vector_ops),
1201
+ ('direct', tx),
1202
+ ]
1203
+
1204
+ for i, (manager_name, manager) in enumerate(managers_to_test):
1205
+ # Check current count (should be initial + number of previous inserts)
1206
+ expected_count = initial_count + i
1207
+ result = await manager.execute(f"SELECT COUNT(*) FROM {test_db}.async_consistency_docs")
1208
+ assert (
1209
+ result.rows[0][0] == expected_count
1210
+ ), f"Manager {manager_name} returned unexpected count {result.rows[0][0]}, expected {expected_count}"
1211
+
1212
+ # Test insert through each manager
1213
+ await manager.execute(
1214
+ f"INSERT INTO {test_db}.async_consistency_docs (title, content, category, priority) VALUES ('{manager_name} Test', 'Content', 'Test', 1)"
1215
+ )
1216
+
1217
+ # Verify insert is visible
1218
+ result = await manager.execute(f"SELECT COUNT(*) FROM {test_db}.async_consistency_docs")
1219
+ assert result.rows[0][0] == expected_count + 1, f"Manager {manager_name} did not see its own insert"