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
@@ -0,0 +1,433 @@
|
|
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
|
+
Online tests for query update functionality
|
17
|
+
"""
|
18
|
+
|
19
|
+
import pytest
|
20
|
+
import os
|
21
|
+
import sys
|
22
|
+
from datetime import datetime
|
23
|
+
|
24
|
+
# Add the matrixone package to the path
|
25
|
+
sys.path.insert(0, os.path.join(os.path.dirname(__file__), '..', '..'))
|
26
|
+
|
27
|
+
from matrixone import Client
|
28
|
+
from matrixone.orm import declarative_base
|
29
|
+
from sqlalchemy import Column, Integer, String, DECIMAL, TIMESTAMP, func
|
30
|
+
|
31
|
+
Base = declarative_base()
|
32
|
+
|
33
|
+
try:
|
34
|
+
from .test_config import online_config
|
35
|
+
except ImportError:
|
36
|
+
# Fallback for when running as standalone script
|
37
|
+
import test_config
|
38
|
+
|
39
|
+
online_config = test_config.online_config
|
40
|
+
|
41
|
+
|
42
|
+
class User(Base):
|
43
|
+
"""User model for testing update operations"""
|
44
|
+
|
45
|
+
__tablename__ = "test_users_update"
|
46
|
+
|
47
|
+
id = Column(Integer, primary_key=True)
|
48
|
+
name = Column(String(100))
|
49
|
+
email = Column(String(100))
|
50
|
+
age = Column(Integer)
|
51
|
+
salary = Column(DECIMAL(10, 2))
|
52
|
+
status = Column(String(50))
|
53
|
+
login_count = Column(Integer, default=0)
|
54
|
+
last_login = Column(TIMESTAMP)
|
55
|
+
created_at = Column(TIMESTAMP, server_default=func.current_timestamp())
|
56
|
+
|
57
|
+
|
58
|
+
class Product(Base):
|
59
|
+
"""Product model for testing update operations"""
|
60
|
+
|
61
|
+
__tablename__ = "test_products_update"
|
62
|
+
|
63
|
+
id = Column(Integer, primary_key=True)
|
64
|
+
name = Column(String(200))
|
65
|
+
price = Column(DECIMAL(10, 2))
|
66
|
+
category = Column(String(50))
|
67
|
+
stock = Column(Integer)
|
68
|
+
active = Column(Integer, default=1) # Using Integer for boolean-like field
|
69
|
+
|
70
|
+
|
71
|
+
class TestQueryUpdateOnline:
|
72
|
+
"""Online tests for query update functionality"""
|
73
|
+
|
74
|
+
@pytest.fixture(scope="class")
|
75
|
+
def test_client(self):
|
76
|
+
"""Create and connect Client for testing"""
|
77
|
+
host, port, user, password, database = online_config.get_connection_params()
|
78
|
+
client = Client()
|
79
|
+
client.connect(host=host, port=port, user=user, password=password, database=database)
|
80
|
+
try:
|
81
|
+
yield client
|
82
|
+
finally:
|
83
|
+
try:
|
84
|
+
client.disconnect()
|
85
|
+
except Exception as e:
|
86
|
+
print(f"Warning: Failed to disconnect client: {e}")
|
87
|
+
|
88
|
+
@pytest.fixture(scope="class")
|
89
|
+
def test_database(self, test_client):
|
90
|
+
"""Set up test database and tables"""
|
91
|
+
test_db = "test_query_update_db"
|
92
|
+
|
93
|
+
# Create test database
|
94
|
+
test_client.execute(f"CREATE DATABASE IF NOT EXISTS {test_db}")
|
95
|
+
test_client.execute(f"USE {test_db}")
|
96
|
+
|
97
|
+
# Create tables using ORM
|
98
|
+
test_client.create_all(Base)
|
99
|
+
|
100
|
+
try:
|
101
|
+
yield test_db
|
102
|
+
finally:
|
103
|
+
# Clean up
|
104
|
+
try:
|
105
|
+
test_client.execute(f"DROP DATABASE IF EXISTS {test_db}")
|
106
|
+
except Exception as e:
|
107
|
+
print(f"Warning: Failed to drop test database: {e}")
|
108
|
+
|
109
|
+
@pytest.fixture(autouse=True, scope="class")
|
110
|
+
def setup_test_data(self, test_client, test_database):
|
111
|
+
"""Set up test data before each test"""
|
112
|
+
# Ensure we're using the test database
|
113
|
+
test_client.execute(f"USE {test_database}")
|
114
|
+
|
115
|
+
# Clear existing data (ignore errors if tables don't exist)
|
116
|
+
try:
|
117
|
+
test_client.query(User).delete()
|
118
|
+
except Exception:
|
119
|
+
pass
|
120
|
+
try:
|
121
|
+
test_client.query(Product).delete()
|
122
|
+
except Exception:
|
123
|
+
pass
|
124
|
+
|
125
|
+
# Insert test users
|
126
|
+
users_data = [
|
127
|
+
{
|
128
|
+
"id": 1,
|
129
|
+
"name": "Alice",
|
130
|
+
"email": "alice@example.com",
|
131
|
+
"age": 25,
|
132
|
+
"salary": 50000.00,
|
133
|
+
"status": "active",
|
134
|
+
"login_count": 5,
|
135
|
+
},
|
136
|
+
{
|
137
|
+
"id": 2,
|
138
|
+
"name": "Bob",
|
139
|
+
"email": "bob@example.com",
|
140
|
+
"age": 30,
|
141
|
+
"salary": 60000.00,
|
142
|
+
"status": "active",
|
143
|
+
"login_count": 10,
|
144
|
+
},
|
145
|
+
{
|
146
|
+
"id": 3,
|
147
|
+
"name": "Charlie",
|
148
|
+
"email": "charlie@example.com",
|
149
|
+
"age": 35,
|
150
|
+
"salary": 70000.00,
|
151
|
+
"status": "inactive",
|
152
|
+
"login_count": 2,
|
153
|
+
},
|
154
|
+
{
|
155
|
+
"id": 4,
|
156
|
+
"name": "David",
|
157
|
+
"email": "david@example.com",
|
158
|
+
"age": 28,
|
159
|
+
"salary": 55000.00,
|
160
|
+
"status": "active",
|
161
|
+
"login_count": 8,
|
162
|
+
},
|
163
|
+
]
|
164
|
+
test_client.batch_insert("test_users_update", users_data)
|
165
|
+
|
166
|
+
# Insert test products
|
167
|
+
products_data = [
|
168
|
+
{"id": 1, "name": "Laptop", "price": 999.99, "category": "Electronics", "stock": 10, "active": 1},
|
169
|
+
{"id": 2, "name": "Phone", "price": 699.99, "category": "Electronics", "stock": 20, "active": 1},
|
170
|
+
{"id": 3, "name": "Book", "price": 29.99, "category": "Education", "stock": 50, "active": 0},
|
171
|
+
{"id": 4, "name": "Tablet", "price": 499.99, "category": "Electronics", "stock": 15, "active": 1},
|
172
|
+
]
|
173
|
+
test_client.batch_insert("test_products_update", products_data)
|
174
|
+
|
175
|
+
def test_simple_update_single_record(self, test_client):
|
176
|
+
"""Test updating a single record with simple values"""
|
177
|
+
# Update Alice's name and email
|
178
|
+
query = test_client.query(User)
|
179
|
+
result = query.update(name="Alice Updated", email="alice.updated@example.com").filter(User.id == 1).execute()
|
180
|
+
|
181
|
+
# Verify the update
|
182
|
+
updated_user = test_client.query(User).filter(User.id == 1).first()
|
183
|
+
assert updated_user.name == "Alice Updated"
|
184
|
+
assert updated_user.email == "alice.updated@example.com"
|
185
|
+
assert updated_user.age == 25 # Should remain unchanged
|
186
|
+
|
187
|
+
def test_update_multiple_records_with_condition(self, test_client):
|
188
|
+
"""Test updating multiple records with a condition"""
|
189
|
+
# Update all active users' status to "premium"
|
190
|
+
query = test_client.query(User)
|
191
|
+
result = query.update(status="premium").filter(User.status == "active").execute()
|
192
|
+
|
193
|
+
# Verify the updates
|
194
|
+
premium_users = test_client.query(User).filter(User.status == "premium").all()
|
195
|
+
assert len(premium_users) == 3 # Alice, Bob, and David
|
196
|
+
|
197
|
+
# Check that inactive user (Charlie) was not updated
|
198
|
+
charlie = test_client.query(User).filter(User.id == 3).first()
|
199
|
+
assert charlie.status == "inactive"
|
200
|
+
|
201
|
+
def test_update_with_sqlalchemy_expressions(self, test_client):
|
202
|
+
"""Test updating with SQLAlchemy expressions"""
|
203
|
+
# Update login count using SQLAlchemy expression
|
204
|
+
query = test_client.query(User)
|
205
|
+
result = query.update(login_count=User.login_count + 1).filter(User.id == 1).execute()
|
206
|
+
|
207
|
+
# Verify the update
|
208
|
+
updated_user = test_client.query(User).filter(User.id == 1).first()
|
209
|
+
assert updated_user.login_count == 6 # Was 5, now 6
|
210
|
+
|
211
|
+
def test_update_with_complex_expressions(self, test_client):
|
212
|
+
"""Test updating with complex SQLAlchemy expressions"""
|
213
|
+
# Update salary with a percentage increase
|
214
|
+
query = test_client.query(User)
|
215
|
+
result = query.update(salary=User.salary * 1.1).filter(User.id == 2).execute()
|
216
|
+
|
217
|
+
# Verify the update
|
218
|
+
updated_user = test_client.query(User).filter(User.id == 2).first()
|
219
|
+
assert updated_user.salary == 66000.00 # 60000 * 1.1
|
220
|
+
|
221
|
+
def test_update_with_multiple_conditions(self, test_client, test_database):
|
222
|
+
"""Test updating with multiple filter conditions"""
|
223
|
+
# Ensure we're using the test database
|
224
|
+
test_client.execute(f"USE {test_database}")
|
225
|
+
|
226
|
+
# Create test records specifically for this test
|
227
|
+
test_client.execute(
|
228
|
+
"""
|
229
|
+
INSERT INTO test_users_update (id, name, email, age, salary, status, login_count)
|
230
|
+
VALUES (200, 'Test User 1', 'test1@example.com', 25, 50000, 'active', 10),
|
231
|
+
(201, 'Test User 2', 'test2@example.com', 30, 60000, 'active', 5),
|
232
|
+
(202, 'Test User 3', 'test3@example.com', 35, 70000, 'active', 8)
|
233
|
+
"""
|
234
|
+
)
|
235
|
+
|
236
|
+
# Update users who are active and have login_count > 5
|
237
|
+
query = test_client.query(User)
|
238
|
+
result = query.update(status="vip").filter(User.status == "active").filter(User.login_count > 5).execute()
|
239
|
+
|
240
|
+
# Verify the updates
|
241
|
+
vip_users = test_client.query(User).filter(User.status == "vip").all()
|
242
|
+
assert len(vip_users) == 2 # User 1 (login_count=10) and User 3 (login_count=8)
|
243
|
+
|
244
|
+
# Check that User 2 (login_count=5) was not updated
|
245
|
+
user2 = test_client.query(User).filter(User.id == 201).first()
|
246
|
+
assert user2.status == "active"
|
247
|
+
|
248
|
+
# Clean up
|
249
|
+
test_client.query(User).filter(User.id.in_([200, 201, 202])).delete()
|
250
|
+
|
251
|
+
def test_update_with_none_values(self, test_client):
|
252
|
+
"""Test updating with None values"""
|
253
|
+
# Update Charlie's last_login to None
|
254
|
+
query = test_client.query(User)
|
255
|
+
result = query.update(last_login=None).filter(User.id == 3).execute()
|
256
|
+
|
257
|
+
# Verify the update
|
258
|
+
updated_user = test_client.query(User).filter(User.id == 3).first()
|
259
|
+
assert updated_user.last_login is None
|
260
|
+
|
261
|
+
def test_update_with_numeric_values(self, test_client):
|
262
|
+
"""Test updating with numeric values"""
|
263
|
+
# Update product price
|
264
|
+
query = test_client.query(Product)
|
265
|
+
result = query.update(price=1099.99).filter(Product.id == 1).execute()
|
266
|
+
|
267
|
+
# Verify the update
|
268
|
+
updated_product = test_client.query(Product).filter(Product.id == 1).first()
|
269
|
+
assert float(updated_product.price) == 1099.99
|
270
|
+
|
271
|
+
def test_update_with_boolean_like_values(self, test_client):
|
272
|
+
"""Test updating with boolean-like values"""
|
273
|
+
# Update product active status
|
274
|
+
query = test_client.query(Product)
|
275
|
+
result = query.update(active=0).filter(Product.id == 2).execute()
|
276
|
+
|
277
|
+
# Verify the update
|
278
|
+
updated_product = test_client.query(Product).filter(Product.id == 2).first()
|
279
|
+
assert updated_product.active == 0
|
280
|
+
|
281
|
+
def test_update_with_string_conditions(self, test_client):
|
282
|
+
"""Test updating with string-based filter conditions"""
|
283
|
+
# Update products in Electronics category
|
284
|
+
query = test_client.query(Product)
|
285
|
+
result = query.update(stock=Product.stock + 5).filter("category = ?", "Electronics").execute()
|
286
|
+
|
287
|
+
# Verify the updates
|
288
|
+
electronics_products = test_client.query(Product).filter("category = ?", "Electronics").all()
|
289
|
+
for product in electronics_products:
|
290
|
+
if product.id == 1: # Laptop
|
291
|
+
assert product.stock == 15 # Was 10, now 15
|
292
|
+
elif product.id == 2: # Phone
|
293
|
+
assert product.stock == 25 # Was 20, now 25
|
294
|
+
elif product.id == 4: # Tablet
|
295
|
+
assert product.stock == 20 # Was 15, now 20
|
296
|
+
|
297
|
+
def test_update_with_mixed_conditions(self, test_client):
|
298
|
+
"""Test updating with mixed SQLAlchemy and string conditions"""
|
299
|
+
# Update users with mixed conditions
|
300
|
+
query = test_client.query(User)
|
301
|
+
result = query.update(age=User.age + 1).filter(User.status == "active").filter("age < ?", 30).execute()
|
302
|
+
|
303
|
+
# Verify the updates
|
304
|
+
young_active_users = test_client.query(User).filter(User.status == "active").filter("age < ?", 30).all()
|
305
|
+
for user in young_active_users:
|
306
|
+
if user.id == 1: # Alice, was 25, now 26
|
307
|
+
assert user.age == 26
|
308
|
+
elif user.id == 4: # David, was 28, now 29
|
309
|
+
assert user.age == 29
|
310
|
+
|
311
|
+
def test_update_no_matching_records(self, test_client):
|
312
|
+
"""Test updating when no records match the condition"""
|
313
|
+
# Try to update a non-existent user
|
314
|
+
query = test_client.query(User)
|
315
|
+
result = query.update(name="Non-existent").filter(User.id == 999).execute()
|
316
|
+
|
317
|
+
# Verify no records were updated
|
318
|
+
non_existent = test_client.query(User).filter(User.id == 999).first()
|
319
|
+
assert non_existent is None
|
320
|
+
|
321
|
+
def test_update_with_transaction(self, test_client, test_database):
|
322
|
+
"""Test updating within a transaction"""
|
323
|
+
# Ensure we're using the test database
|
324
|
+
test_client.execute(f"USE {test_database}")
|
325
|
+
|
326
|
+
# Create a test record specifically for this test
|
327
|
+
test_client.execute(
|
328
|
+
"""
|
329
|
+
INSERT INTO test_users_update (id, name, email, age, salary, status, login_count)
|
330
|
+
VALUES (100, 'Transaction Test User', 'transaction@test.com', 25, 50000, 'active', 0)
|
331
|
+
"""
|
332
|
+
)
|
333
|
+
|
334
|
+
with test_client.transaction() as tx:
|
335
|
+
# Ensure we're using the test database in transaction context
|
336
|
+
test_client.execute(f"USE {test_database}")
|
337
|
+
# Update the test record in a transaction
|
338
|
+
query = test_client.query(User)
|
339
|
+
query.update(status="transaction_committed").filter(User.id == 100).execute()
|
340
|
+
|
341
|
+
# Verify the update was committed
|
342
|
+
updated_user = test_client.query(User).filter(User.id == 100).first()
|
343
|
+
assert updated_user is not None
|
344
|
+
assert updated_user.status == "transaction_committed"
|
345
|
+
|
346
|
+
# Clean up
|
347
|
+
test_client.query(User).filter(User.id == 100).delete()
|
348
|
+
|
349
|
+
def test_update_with_rollback(self, test_client, test_database):
|
350
|
+
"""Test updating with transaction rollback"""
|
351
|
+
# Ensure we're using the test database
|
352
|
+
test_client.execute(f"USE {test_database}")
|
353
|
+
|
354
|
+
# Create a test record specifically for this test
|
355
|
+
test_client.execute(
|
356
|
+
"""
|
357
|
+
INSERT INTO test_users_update (id, name, email, age, salary, status, login_count)
|
358
|
+
VALUES (101, 'Rollback Test User', 'rollback@test.com', 30, 60000, 'original_status', 0)
|
359
|
+
"""
|
360
|
+
)
|
361
|
+
|
362
|
+
# Verify initial state
|
363
|
+
user = test_client.query(User).filter(User.id == 101).first()
|
364
|
+
assert user is not None
|
365
|
+
assert user.status == "original_status"
|
366
|
+
|
367
|
+
# Test transaction rollback
|
368
|
+
try:
|
369
|
+
with test_client.transaction() as tx:
|
370
|
+
# Ensure we're using the test database in transaction context
|
371
|
+
tx.execute(f"USE {test_database}")
|
372
|
+
# Update the test record using transaction connection
|
373
|
+
tx.execute("UPDATE test_users_update SET status = 'rollback_test' WHERE id = 101")
|
374
|
+
|
375
|
+
# Verify the update happened within the transaction
|
376
|
+
result = tx.execute("SELECT status FROM test_users_update WHERE id = 101")
|
377
|
+
user_in_tx = result.fetchone()
|
378
|
+
assert user_in_tx[0] == "rollback_test"
|
379
|
+
|
380
|
+
# Force rollback by raising an exception
|
381
|
+
raise Exception("Force rollback")
|
382
|
+
except Exception:
|
383
|
+
pass
|
384
|
+
|
385
|
+
# Verify the update was rolled back - status should be back to "original_status"
|
386
|
+
user = test_client.query(User).filter(User.id == 101).first()
|
387
|
+
assert user is not None
|
388
|
+
assert user.status == "original_status"
|
389
|
+
|
390
|
+
# Clean up
|
391
|
+
test_client.query(User).filter(User.id == 101).delete()
|
392
|
+
|
393
|
+
def test_update_with_explain(self, test_client):
|
394
|
+
"""Test explaining an update query"""
|
395
|
+
query = test_client.query(User)
|
396
|
+
explain_result = query.update(name="Explain Test").filter(User.id == 1).explain()
|
397
|
+
|
398
|
+
# Verify explain returns a result
|
399
|
+
assert explain_result is not None
|
400
|
+
assert len(explain_result.rows) > 0
|
401
|
+
|
402
|
+
def test_update_with_to_sql(self, test_client):
|
403
|
+
"""Test generating SQL for an update query"""
|
404
|
+
query = test_client.query(User)
|
405
|
+
sql = query.update(name="SQL Test", email="sql@example.com").filter(User.id == 1).to_sql()
|
406
|
+
|
407
|
+
# Verify SQL structure
|
408
|
+
assert "UPDATE test_users_update SET" in sql
|
409
|
+
assert "name = 'SQL Test'" in sql
|
410
|
+
assert "email = 'sql@example.com'" in sql
|
411
|
+
assert "WHERE id = 1" in sql
|
412
|
+
|
413
|
+
def test_update_with_explain_analyze(self, test_client):
|
414
|
+
"""Test explaining and analyzing an update query"""
|
415
|
+
query = test_client.query(User)
|
416
|
+
explain_result = query.update(name="Explain Analyze Test").filter(User.id == 1).explain_analyze()
|
417
|
+
|
418
|
+
# Verify explain analyze returns a result
|
419
|
+
assert explain_result is not None
|
420
|
+
assert len(explain_result.rows) > 0
|
421
|
+
|
422
|
+
def test_update_with_verbose_explain(self, test_client):
|
423
|
+
"""Test verbose explain for an update query"""
|
424
|
+
query = test_client.query(User)
|
425
|
+
explain_result = query.update(name="Verbose Explain Test").filter(User.id == 1).explain(verbose=True)
|
426
|
+
|
427
|
+
# Verify verbose explain returns a result
|
428
|
+
assert explain_result is not None
|
429
|
+
assert len(explain_result.rows) > 0
|
430
|
+
|
431
|
+
|
432
|
+
if __name__ == "__main__":
|
433
|
+
pytest.main([__file__])
|