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,352 @@
|
|
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 PubSub operations
|
17
|
+
|
18
|
+
These tests are inspired by example_08_pubsub_operations.py
|
19
|
+
"""
|
20
|
+
|
21
|
+
import pytest
|
22
|
+
import time
|
23
|
+
from datetime import datetime
|
24
|
+
from matrixone import Client, AsyncClient
|
25
|
+
from matrixone.logger import create_default_logger
|
26
|
+
from .test_config import online_config
|
27
|
+
|
28
|
+
|
29
|
+
@pytest.mark.online
|
30
|
+
class TestPubSubOperations:
|
31
|
+
"""Test PubSub operations functionality"""
|
32
|
+
|
33
|
+
def test_basic_pubsub_operations(self, test_client):
|
34
|
+
"""Test basic PubSub operations within the same account"""
|
35
|
+
# Create test database and tables
|
36
|
+
test_client.execute("CREATE DATABASE IF NOT EXISTS pubsub_test")
|
37
|
+
test_client.execute("USE pubsub_test")
|
38
|
+
|
39
|
+
test_client.execute(
|
40
|
+
"""
|
41
|
+
CREATE TABLE IF NOT EXISTS products (
|
42
|
+
id INT PRIMARY KEY AUTO_INCREMENT,
|
43
|
+
name VARCHAR(100) NOT NULL,
|
44
|
+
price DECIMAL(10,2),
|
45
|
+
category VARCHAR(50),
|
46
|
+
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
|
47
|
+
)
|
48
|
+
"""
|
49
|
+
)
|
50
|
+
|
51
|
+
test_client.execute(
|
52
|
+
"""
|
53
|
+
CREATE TABLE IF NOT EXISTS orders (
|
54
|
+
id INT PRIMARY KEY AUTO_INCREMENT,
|
55
|
+
product_id INT,
|
56
|
+
quantity INT,
|
57
|
+
total_price DECIMAL(10,2),
|
58
|
+
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
|
59
|
+
)
|
60
|
+
"""
|
61
|
+
)
|
62
|
+
|
63
|
+
try:
|
64
|
+
# Test 1: Basic PubSub setup (as per examples)
|
65
|
+
# Insert sample data
|
66
|
+
products_data = [
|
67
|
+
("Laptop", 999.99, "Electronics"),
|
68
|
+
("Mouse", 29.99, "Electronics"),
|
69
|
+
("Keyboard", 79.99, "Electronics"),
|
70
|
+
("Monitor", 299.99, "Electronics"),
|
71
|
+
]
|
72
|
+
|
73
|
+
for name, price, category in products_data:
|
74
|
+
test_client.execute(f"INSERT INTO products (name, price, category) VALUES ('{name}', {price}, '{category}')")
|
75
|
+
|
76
|
+
# Test 2: List publications (as per examples - should work even if empty)
|
77
|
+
publications = test_client.pubsub.list_publications()
|
78
|
+
assert isinstance(publications, list)
|
79
|
+
|
80
|
+
# Test 3: List subscriptions (as per examples - should work even if empty)
|
81
|
+
subscriptions = test_client.pubsub.list_subscriptions()
|
82
|
+
assert isinstance(subscriptions, list)
|
83
|
+
|
84
|
+
# Test 4: Data operations
|
85
|
+
test_client.execute("INSERT INTO orders (product_id, quantity, total_price) VALUES (1, 2, 199.98)")
|
86
|
+
|
87
|
+
# Verify data
|
88
|
+
result = test_client.execute("SELECT COUNT(*) FROM products")
|
89
|
+
assert result.rows[0][0] == 4
|
90
|
+
|
91
|
+
result = test_client.execute("SELECT COUNT(*) FROM orders")
|
92
|
+
assert result.rows[0][0] == 1
|
93
|
+
|
94
|
+
finally:
|
95
|
+
# Cleanup
|
96
|
+
try:
|
97
|
+
test_client.pubsub.drop_subscription("product_subscription")
|
98
|
+
test_client.pubsub.drop_publication("product_updates")
|
99
|
+
except Exception as e:
|
100
|
+
print(f"Warning: Failed to cleanup pubsub resources: {e}")
|
101
|
+
# Don't ignore - this could indicate resource leaks
|
102
|
+
|
103
|
+
test_client.execute("DROP DATABASE IF EXISTS pubsub_test")
|
104
|
+
|
105
|
+
def test_pubsub_with_multiple_tables(self, test_client):
|
106
|
+
"""Test PubSub with multiple tables"""
|
107
|
+
# Create test database
|
108
|
+
test_client.execute("CREATE DATABASE IF NOT EXISTS pubsub_multi_test")
|
109
|
+
test_client.execute("USE pubsub_multi_test")
|
110
|
+
|
111
|
+
# Create multiple tables
|
112
|
+
test_client.execute(
|
113
|
+
"""
|
114
|
+
CREATE TABLE IF NOT EXISTS users (
|
115
|
+
id INT PRIMARY KEY AUTO_INCREMENT,
|
116
|
+
username VARCHAR(50) NOT NULL,
|
117
|
+
email VARCHAR(100) NOT NULL,
|
118
|
+
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
|
119
|
+
)
|
120
|
+
"""
|
121
|
+
)
|
122
|
+
|
123
|
+
test_client.execute(
|
124
|
+
"""
|
125
|
+
CREATE TABLE IF NOT EXISTS posts (
|
126
|
+
id INT PRIMARY KEY AUTO_INCREMENT,
|
127
|
+
user_id INT,
|
128
|
+
title VARCHAR(200) NOT NULL,
|
129
|
+
content TEXT,
|
130
|
+
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
|
131
|
+
)
|
132
|
+
"""
|
133
|
+
)
|
134
|
+
|
135
|
+
test_client.execute(
|
136
|
+
"""
|
137
|
+
CREATE TABLE IF NOT EXISTS comments (
|
138
|
+
id INT PRIMARY KEY AUTO_INCREMENT,
|
139
|
+
post_id INT,
|
140
|
+
user_id INT,
|
141
|
+
content TEXT,
|
142
|
+
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
|
143
|
+
)
|
144
|
+
"""
|
145
|
+
)
|
146
|
+
|
147
|
+
try:
|
148
|
+
# Test multi-table PubSub operations (as per examples)
|
149
|
+
# Insert sample data
|
150
|
+
test_client.execute("INSERT INTO users (username, email) VALUES ('testuser', 'test@example.com')")
|
151
|
+
test_client.execute("INSERT INTO posts (user_id, title, content) VALUES (1, 'Test Post', 'This is a test post')")
|
152
|
+
test_client.execute("INSERT INTO comments (post_id, user_id, content) VALUES (1, 1, 'This is a test comment')")
|
153
|
+
|
154
|
+
# Test list operations (as per examples - should work even if empty)
|
155
|
+
publications = test_client.pubsub.list_publications()
|
156
|
+
assert isinstance(publications, list)
|
157
|
+
|
158
|
+
subscriptions = test_client.pubsub.list_subscriptions()
|
159
|
+
assert isinstance(subscriptions, list)
|
160
|
+
|
161
|
+
# Verify data across all tables
|
162
|
+
result = test_client.execute("SELECT COUNT(*) FROM users")
|
163
|
+
assert result.rows[0][0] == 1
|
164
|
+
|
165
|
+
result = test_client.execute("SELECT COUNT(*) FROM posts")
|
166
|
+
assert result.rows[0][0] == 1
|
167
|
+
|
168
|
+
result = test_client.execute("SELECT COUNT(*) FROM comments")
|
169
|
+
assert result.rows[0][0] == 1
|
170
|
+
|
171
|
+
finally:
|
172
|
+
# Cleanup
|
173
|
+
try:
|
174
|
+
test_client.pubsub.drop_publication("multi_table_publication")
|
175
|
+
except Exception as e:
|
176
|
+
print(f"Warning: Failed to cleanup multi-table publication: {e}")
|
177
|
+
|
178
|
+
test_client.execute("DROP DATABASE IF EXISTS pubsub_multi_test")
|
179
|
+
|
180
|
+
def test_pubsub_error_handling(self, test_client):
|
181
|
+
"""Test PubSub error handling"""
|
182
|
+
# Test creating publication with invalid parameters
|
183
|
+
try:
|
184
|
+
test_client.pubsub.create_database_publication(
|
185
|
+
name="", # Empty name should fail
|
186
|
+
database="nonexistent_db",
|
187
|
+
account=online_config.get_test_account(),
|
188
|
+
)
|
189
|
+
assert False, "Should have failed with empty name"
|
190
|
+
except Exception:
|
191
|
+
pass # Expected to fail
|
192
|
+
|
193
|
+
# Test creating subscription with invalid parameters
|
194
|
+
try:
|
195
|
+
test_client.pubsub.create_subscription(
|
196
|
+
name="", # Empty name should fail
|
197
|
+
publication="nonexistent_publication",
|
198
|
+
account=online_config.get_test_account(),
|
199
|
+
)
|
200
|
+
assert False, "Should have failed with empty name"
|
201
|
+
except Exception:
|
202
|
+
pass # Expected to fail
|
203
|
+
|
204
|
+
# Test dropping non-existent publication
|
205
|
+
try:
|
206
|
+
test_client.pubsub.drop_publication("nonexistent_publication")
|
207
|
+
assert False, "Should have failed with non-existent publication"
|
208
|
+
except Exception:
|
209
|
+
pass # Expected to fail
|
210
|
+
|
211
|
+
# Test dropping non-existent subscription
|
212
|
+
try:
|
213
|
+
test_client.pubsub.drop_subscription("nonexistent_subscription")
|
214
|
+
assert False, "Should have failed with non-existent subscription"
|
215
|
+
except Exception:
|
216
|
+
pass # Expected to fail
|
217
|
+
|
218
|
+
def test_pubsub_listing_operations(self, test_client):
|
219
|
+
"""Test PubSub listing operations"""
|
220
|
+
# Create test database
|
221
|
+
test_client.execute("CREATE DATABASE IF NOT EXISTS pubsub_list_test")
|
222
|
+
test_client.execute("USE pubsub_list_test")
|
223
|
+
|
224
|
+
test_client.execute(
|
225
|
+
"""
|
226
|
+
CREATE TABLE IF NOT EXISTS test_table (
|
227
|
+
id INT PRIMARY KEY AUTO_INCREMENT,
|
228
|
+
name VARCHAR(50) NOT NULL
|
229
|
+
)
|
230
|
+
"""
|
231
|
+
)
|
232
|
+
|
233
|
+
try:
|
234
|
+
# Test listing operations (as per examples - should work even if empty)
|
235
|
+
# Test listing publications
|
236
|
+
publications = test_client.pubsub.list_publications()
|
237
|
+
assert isinstance(publications, list)
|
238
|
+
|
239
|
+
# Test listing subscriptions
|
240
|
+
subscriptions = test_client.pubsub.list_subscriptions()
|
241
|
+
assert isinstance(subscriptions, list)
|
242
|
+
|
243
|
+
# Insert test data
|
244
|
+
test_client.execute("INSERT INTO test_table (name) VALUES ('test_item')")
|
245
|
+
|
246
|
+
# Verify data
|
247
|
+
result = test_client.execute("SELECT COUNT(*) FROM test_table")
|
248
|
+
assert result.rows[0][0] == 1
|
249
|
+
|
250
|
+
finally:
|
251
|
+
# Cleanup
|
252
|
+
try:
|
253
|
+
test_client.pubsub.drop_subscription("list_test_subscription")
|
254
|
+
test_client.pubsub.drop_publication("list_test_publication")
|
255
|
+
except Exception as e:
|
256
|
+
print(f"Warning: Failed to cleanup list test resources: {e}")
|
257
|
+
|
258
|
+
test_client.execute("DROP DATABASE IF EXISTS pubsub_list_test")
|
259
|
+
|
260
|
+
@pytest.mark.asyncio
|
261
|
+
async def test_async_pubsub_operations(self, test_async_client):
|
262
|
+
"""Test async PubSub operations"""
|
263
|
+
# Create test database
|
264
|
+
await test_async_client.execute("CREATE DATABASE IF NOT EXISTS async_pubsub_test")
|
265
|
+
await test_async_client.execute("USE async_pubsub_test")
|
266
|
+
|
267
|
+
await test_async_client.execute(
|
268
|
+
"""
|
269
|
+
CREATE TABLE IF NOT EXISTS async_test_table (
|
270
|
+
id INT PRIMARY KEY AUTO_INCREMENT,
|
271
|
+
name VARCHAR(50) NOT NULL,
|
272
|
+
value INT
|
273
|
+
)
|
274
|
+
"""
|
275
|
+
)
|
276
|
+
|
277
|
+
try:
|
278
|
+
# Test async PubSub operations (as per examples)
|
279
|
+
# Test list operations (should work even if empty)
|
280
|
+
publications = await test_async_client.pubsub.list_publications()
|
281
|
+
assert isinstance(publications, list)
|
282
|
+
|
283
|
+
subscriptions = await test_async_client.pubsub.list_subscriptions()
|
284
|
+
assert isinstance(subscriptions, list)
|
285
|
+
|
286
|
+
# Test async data operations
|
287
|
+
await test_async_client.execute("INSERT INTO async_test_table (name, value) VALUES ('async_test', 42)")
|
288
|
+
|
289
|
+
# Verify data
|
290
|
+
result = await test_async_client.execute("SELECT COUNT(*) FROM async_test_table")
|
291
|
+
assert result.rows[0][0] == 1
|
292
|
+
|
293
|
+
finally:
|
294
|
+
# Cleanup
|
295
|
+
try:
|
296
|
+
await test_async_client.pubsub.drop_subscription("async_test_subscription")
|
297
|
+
await test_async_client.pubsub.drop_publication("async_test_publication")
|
298
|
+
except Exception as e:
|
299
|
+
print(f"Warning: Failed to cleanup async pubsub resources: {e}")
|
300
|
+
|
301
|
+
await test_async_client.execute("DROP DATABASE IF EXISTS async_pubsub_test")
|
302
|
+
|
303
|
+
def test_pubsub_with_logging(self, connection_params):
|
304
|
+
"""Test PubSub operations with custom logging"""
|
305
|
+
host, port, user, password, database = connection_params
|
306
|
+
|
307
|
+
# Create logger
|
308
|
+
logger = create_default_logger()
|
309
|
+
|
310
|
+
# Create client with logging
|
311
|
+
client = Client()
|
312
|
+
client.connect(host=host, port=port, user=user, password=password, database=database)
|
313
|
+
|
314
|
+
try:
|
315
|
+
# Create test database
|
316
|
+
client.execute("CREATE DATABASE IF NOT EXISTS pubsub_log_test")
|
317
|
+
client.execute("USE pubsub_log_test")
|
318
|
+
|
319
|
+
client.execute(
|
320
|
+
"""
|
321
|
+
CREATE TABLE IF NOT EXISTS log_test_table (
|
322
|
+
id INT PRIMARY KEY AUTO_INCREMENT,
|
323
|
+
name VARCHAR(50) NOT NULL
|
324
|
+
)
|
325
|
+
"""
|
326
|
+
)
|
327
|
+
|
328
|
+
# Test PubSub operations with logging (as per examples)
|
329
|
+
# Test list operations (should work even if empty)
|
330
|
+
publications = client.pubsub.list_publications()
|
331
|
+
assert isinstance(publications, list)
|
332
|
+
|
333
|
+
subscriptions = client.pubsub.list_subscriptions()
|
334
|
+
assert isinstance(subscriptions, list)
|
335
|
+
|
336
|
+
# Test data operations with logging
|
337
|
+
client.execute("INSERT INTO log_test_table (name) VALUES ('log_test')")
|
338
|
+
|
339
|
+
# Verify data
|
340
|
+
result = client.execute("SELECT COUNT(*) FROM log_test_table")
|
341
|
+
assert result.rows[0][0] == 1
|
342
|
+
|
343
|
+
# Cleanup
|
344
|
+
try:
|
345
|
+
client.pubsub.drop_publication("log_test_publication")
|
346
|
+
except Exception as e:
|
347
|
+
print(f"Warning: Failed to cleanup logging test publication: {e}")
|
348
|
+
|
349
|
+
client.execute("DROP DATABASE IF EXISTS pubsub_log_test")
|
350
|
+
|
351
|
+
finally:
|
352
|
+
client.disconnect()
|
@@ -0,0 +1,225 @@
|
|
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 cases for SQLAlchemy-style query methods (all, one, first, scalar, one_or_none)
|
17
|
+
"""
|
18
|
+
|
19
|
+
import pytest
|
20
|
+
from sqlalchemy import Column, Integer, String, create_engine
|
21
|
+
from sqlalchemy.exc import NoResultFound, MultipleResultsFound
|
22
|
+
|
23
|
+
from matrixone import Client
|
24
|
+
from matrixone.orm import declarative_base
|
25
|
+
from .test_config import online_config
|
26
|
+
|
27
|
+
Base = declarative_base()
|
28
|
+
|
29
|
+
|
30
|
+
class QueryTestUser(Base):
|
31
|
+
__tablename__ = 'test_query_users'
|
32
|
+
id = Column(Integer, primary_key=True)
|
33
|
+
name = Column(String(50))
|
34
|
+
age = Column(Integer)
|
35
|
+
|
36
|
+
|
37
|
+
class TestQueryMethods:
|
38
|
+
"""Test SQLAlchemy-style query methods"""
|
39
|
+
|
40
|
+
@pytest.fixture(autouse=True)
|
41
|
+
def setup_and_cleanup(self):
|
42
|
+
"""Setup test data and cleanup after tests"""
|
43
|
+
# Setup
|
44
|
+
self.client = Client()
|
45
|
+
host, port, user, password, database = online_config.get_connection_params()
|
46
|
+
self.client.connect(host=host, port=port, user=user, password=password, database=database)
|
47
|
+
|
48
|
+
# Create table (with checkfirst to avoid errors if table exists)
|
49
|
+
try:
|
50
|
+
self.client.create_table(QueryTestUser)
|
51
|
+
except Exception:
|
52
|
+
# Table might already exist, try to drop and recreate
|
53
|
+
try:
|
54
|
+
self.client.drop_table(QueryTestUser)
|
55
|
+
self.client.create_table(QueryTestUser)
|
56
|
+
except Exception:
|
57
|
+
pass # Continue with existing table
|
58
|
+
|
59
|
+
# Insert test data
|
60
|
+
users_data = [
|
61
|
+
{"id": 1, "name": "Alice", "age": 25},
|
62
|
+
{"id": 2, "name": "Bob", "age": 30},
|
63
|
+
{"id": 3, "name": "Charlie", "age": 35},
|
64
|
+
]
|
65
|
+
for user_data in users_data:
|
66
|
+
self.client.insert(QueryTestUser.__tablename__, user_data)
|
67
|
+
|
68
|
+
yield
|
69
|
+
|
70
|
+
# Cleanup
|
71
|
+
try:
|
72
|
+
self.client.drop_table(QueryTestUser)
|
73
|
+
except:
|
74
|
+
pass
|
75
|
+
self.client.disconnect()
|
76
|
+
|
77
|
+
def test_all_method(self):
|
78
|
+
"""Test all() method returns all results"""
|
79
|
+
results = self.client.query(QueryTestUser).all()
|
80
|
+
assert len(results) == 3
|
81
|
+
assert all(isinstance(user, QueryTestUser) for user in results)
|
82
|
+
assert results[0].name == 'Alice'
|
83
|
+
assert results[1].name == 'Bob'
|
84
|
+
assert results[2].name == 'Charlie'
|
85
|
+
|
86
|
+
def test_first_method(self):
|
87
|
+
"""Test first() method returns first result or None"""
|
88
|
+
# Test with results
|
89
|
+
result = self.client.query(QueryTestUser).first()
|
90
|
+
assert isinstance(result, QueryTestUser)
|
91
|
+
assert result.name == 'Alice'
|
92
|
+
|
93
|
+
# Test with no results
|
94
|
+
result = self.client.query(QueryTestUser).filter(QueryTestUser.id == 999).first()
|
95
|
+
assert result is None
|
96
|
+
|
97
|
+
def test_one_method_success(self):
|
98
|
+
"""Test one() method with exactly one result"""
|
99
|
+
result = self.client.query(QueryTestUser).filter(QueryTestUser.id == 1).one()
|
100
|
+
assert isinstance(result, QueryTestUser)
|
101
|
+
assert result.name == 'Alice'
|
102
|
+
|
103
|
+
def test_one_method_no_results(self):
|
104
|
+
"""Test one() method with no results raises NoResultFound"""
|
105
|
+
with pytest.raises(NoResultFound):
|
106
|
+
self.client.query(QueryTestUser).filter(QueryTestUser.id == 999).one()
|
107
|
+
|
108
|
+
def test_one_method_multiple_results(self):
|
109
|
+
"""Test one() method with multiple results raises MultipleResultsFound"""
|
110
|
+
with pytest.raises(MultipleResultsFound):
|
111
|
+
self.client.query(QueryTestUser).filter(QueryTestUser.age > 20).one()
|
112
|
+
|
113
|
+
def test_one_or_none_method_success(self):
|
114
|
+
"""Test one_or_none() method with exactly one result"""
|
115
|
+
result = self.client.query(QueryTestUser).filter(QueryTestUser.id == 1).one_or_none()
|
116
|
+
assert isinstance(result, QueryTestUser)
|
117
|
+
assert result.name == 'Alice'
|
118
|
+
|
119
|
+
def test_one_or_none_method_no_results(self):
|
120
|
+
"""Test one_or_none() method with no results returns None"""
|
121
|
+
result = self.client.query(QueryTestUser).filter(QueryTestUser.id == 999).one_or_none()
|
122
|
+
assert result is None
|
123
|
+
|
124
|
+
def test_one_or_none_method_multiple_results(self):
|
125
|
+
"""Test one_or_none() method with multiple results raises MultipleResultsFound"""
|
126
|
+
with pytest.raises(MultipleResultsFound):
|
127
|
+
self.client.query(QueryTestUser).filter(QueryTestUser.age > 20).one_or_none()
|
128
|
+
|
129
|
+
def test_scalar_method_with_model(self):
|
130
|
+
"""Test scalar() method returns first column value from model"""
|
131
|
+
# Test with primary key (first column)
|
132
|
+
result = self.client.query(QueryTestUser).filter(QueryTestUser.id == 1).scalar()
|
133
|
+
assert result == 1
|
134
|
+
|
135
|
+
# Test with no results
|
136
|
+
result = self.client.query(QueryTestUser).filter(QueryTestUser.id == 999).scalar()
|
137
|
+
assert result is None
|
138
|
+
|
139
|
+
def test_scalar_method_with_select_columns(self):
|
140
|
+
"""Test scalar() method with custom select columns"""
|
141
|
+
# Select specific column
|
142
|
+
result = self.client.query(QueryTestUser).select(QueryTestUser.name).filter(QueryTestUser.id == 1).scalar()
|
143
|
+
assert result == 'Alice'
|
144
|
+
|
145
|
+
# Select count - use count() method directly, not with select
|
146
|
+
result = self.client.query(QueryTestUser).count()
|
147
|
+
assert result == 3
|
148
|
+
|
149
|
+
def test_scalar_method_with_aggregate(self):
|
150
|
+
"""Test scalar() method with aggregate functions"""
|
151
|
+
from sqlalchemy import func
|
152
|
+
|
153
|
+
# Count all users
|
154
|
+
result = self.client.query(QueryTestUser).select(func.count(QueryTestUser.id)).scalar()
|
155
|
+
assert result == 3
|
156
|
+
|
157
|
+
# Average age
|
158
|
+
result = self.client.query(QueryTestUser).select(func.avg(QueryTestUser.age)).scalar()
|
159
|
+
assert result == 30.0 # (25 + 30 + 35) / 3
|
160
|
+
|
161
|
+
def test_methods_with_filters(self):
|
162
|
+
"""Test query methods work with various filters"""
|
163
|
+
# Test with multiple filters - use AND to combine conditions
|
164
|
+
from sqlalchemy import and_
|
165
|
+
|
166
|
+
result = self.client.query(QueryTestUser).filter(and_(QueryTestUser.age > 25, QueryTestUser.name.like('B%'))).one()
|
167
|
+
assert result.name == 'Bob'
|
168
|
+
assert result.age == 30
|
169
|
+
|
170
|
+
def test_methods_with_ordering(self):
|
171
|
+
"""Test query methods work with ordering"""
|
172
|
+
# Test first() with ordering
|
173
|
+
result = self.client.query(QueryTestUser).order_by(QueryTestUser.age.desc()).first()
|
174
|
+
assert result.name == 'Charlie'
|
175
|
+
assert result.age == 35
|
176
|
+
|
177
|
+
# Test scalar() with ordering
|
178
|
+
result = self.client.query(QueryTestUser).select(QueryTestUser.name).order_by(QueryTestUser.age.desc()).scalar()
|
179
|
+
assert result == 'Charlie'
|
180
|
+
|
181
|
+
def test_methods_with_limit(self):
|
182
|
+
"""Test query methods work with limit"""
|
183
|
+
# Test all() with limit
|
184
|
+
results = self.client.query(QueryTestUser).limit(2).all()
|
185
|
+
assert len(results) == 2
|
186
|
+
|
187
|
+
# Test first() with limit (should still return only one)
|
188
|
+
result = self.client.query(QueryTestUser).limit(2).first()
|
189
|
+
assert isinstance(result, QueryTestUser)
|
190
|
+
|
191
|
+
def test_count_method(self):
|
192
|
+
"""Test count() method"""
|
193
|
+
# Count all
|
194
|
+
count = self.client.query(QueryTestUser).count()
|
195
|
+
assert count == 3
|
196
|
+
|
197
|
+
# Count with filter
|
198
|
+
count = self.client.query(QueryTestUser).filter(QueryTestUser.age > 25).count()
|
199
|
+
assert count == 2
|
200
|
+
|
201
|
+
def test_methods_consistency(self):
|
202
|
+
"""Test that methods are consistent with each other"""
|
203
|
+
# Test that first() and all()[0] return same result
|
204
|
+
first_result = self.client.query(QueryTestUser).order_by(QueryTestUser.id).first()
|
205
|
+
all_results = self.client.query(QueryTestUser).order_by(QueryTestUser.id).all()
|
206
|
+
assert first_result.id == all_results[0].id
|
207
|
+
|
208
|
+
# Test that one() and one_or_none() return same result when exactly one exists
|
209
|
+
one_result = self.client.query(QueryTestUser).filter(QueryTestUser.id == 1).one()
|
210
|
+
one_or_none_result = self.client.query(QueryTestUser).filter(QueryTestUser.id == 1).one_or_none()
|
211
|
+
assert one_result.id == one_or_none_result.id
|
212
|
+
|
213
|
+
def test_error_messages(self):
|
214
|
+
"""Test that error messages are informative"""
|
215
|
+
# Test NoResultFound message
|
216
|
+
with pytest.raises(NoResultFound, match="No row was found for one\\(\\)"):
|
217
|
+
self.client.query(QueryTestUser).filter(QueryTestUser.id == 999).one()
|
218
|
+
|
219
|
+
# Test MultipleResultsFound message
|
220
|
+
with pytest.raises(MultipleResultsFound, match="Multiple rows were found for one_or_none\\(\\)"):
|
221
|
+
self.client.query(QueryTestUser).filter(QueryTestUser.age > 20).one_or_none()
|
222
|
+
|
223
|
+
|
224
|
+
if __name__ == '__main__':
|
225
|
+
pytest.main([__file__])
|