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,540 @@
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 MatrixOneQuery and AsyncMatrixOneQuery ORM functionality.
17
+ Comprehensive online tests for SQLAlchemy-style ORM operations.
18
+ """
19
+
20
+ import pytest
21
+ import pytest_asyncio
22
+ import asyncio
23
+ import sys
24
+ import os
25
+ from datetime import datetime
26
+ from typing import List, Optional
27
+
28
+ # Add the project root to Python path
29
+ sys.path.insert(0, os.path.abspath(os.path.join(os.path.dirname(__file__), '..', '..')))
30
+
31
+ from matrixone import Client, AsyncClient, SnapshotManager, SnapshotLevel
32
+ from sqlalchemy import Column, Integer, String, DECIMAL, TIMESTAMP
33
+ from matrixone.orm import desc, declarative_base
34
+
35
+ Base = declarative_base()
36
+ from .test_config import online_config
37
+
38
+
39
+ # Define test models
40
+ class User(Base):
41
+ """User model for testing"""
42
+
43
+ __tablename__ = "test_users"
44
+
45
+ id = Column(Integer, primary_key=True)
46
+ name = Column(String(100))
47
+ email = Column(String(100))
48
+ age = Column(Integer)
49
+ department = Column(String(50))
50
+ salary = Column(DECIMAL(10, 2))
51
+ created_at = Column(TIMESTAMP)
52
+
53
+
54
+ class Product(Base):
55
+ """Product model for testing"""
56
+
57
+ __tablename__ = "test_products"
58
+
59
+ id = Column(Integer, primary_key=True)
60
+ name = Column(String(100))
61
+ price = Column(DECIMAL(10, 2))
62
+ category = Column(String(50))
63
+ stock = Column(Integer)
64
+ created_at = Column(TIMESTAMP)
65
+
66
+
67
+ class Order(Base):
68
+ """Order model for testing"""
69
+
70
+ __tablename__ = "test_orders"
71
+
72
+ id = Column(Integer, primary_key=True)
73
+ user_id = Column(Integer)
74
+ product_id = Column(Integer)
75
+ quantity = Column(Integer)
76
+ total_amount = Column(DECIMAL(10, 2))
77
+ order_date = Column(TIMESTAMP)
78
+
79
+
80
+ class TestMatrixOneQueryORM:
81
+ """Test MatrixOneQuery ORM functionality"""
82
+
83
+ @pytest.fixture(scope="class")
84
+ def client(self):
85
+ """Create and connect MatrixOne client."""
86
+ client = Client()
87
+ host, port, user, password, database = online_config.get_connection_params()
88
+ client.connect(host=host, port=port, user=user, password=password, database=database)
89
+ yield client
90
+ if hasattr(client, 'disconnect'):
91
+ client.disconnect()
92
+
93
+ @pytest.fixture(scope="class")
94
+ def test_data_setup(self, client):
95
+ """Set up test data"""
96
+ # Clean up any existing test data
97
+ try:
98
+ client.execute("DROP TABLE IF EXISTS test_orders")
99
+ client.execute("DROP TABLE IF EXISTS test_products")
100
+ client.execute("DROP TABLE IF EXISTS test_users")
101
+ except:
102
+ pass
103
+
104
+ # Create test tables
105
+ client.execute(
106
+ """
107
+ CREATE TABLE test_users (
108
+ id INT PRIMARY KEY,
109
+ name VARCHAR(100),
110
+ email VARCHAR(100),
111
+ age INT,
112
+ department VARCHAR(50),
113
+ salary DECIMAL(10,2),
114
+ created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
115
+ )
116
+ """
117
+ )
118
+
119
+ client.execute(
120
+ """
121
+ CREATE TABLE test_products (
122
+ id INT PRIMARY KEY,
123
+ name VARCHAR(100),
124
+ price DECIMAL(10,2),
125
+ category VARCHAR(50),
126
+ stock INT,
127
+ created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
128
+ )
129
+ """
130
+ )
131
+
132
+ client.execute(
133
+ """
134
+ CREATE TABLE test_orders (
135
+ id INT PRIMARY KEY,
136
+ user_id INT,
137
+ product_id INT,
138
+ quantity INT,
139
+ total_amount DECIMAL(10,2),
140
+ order_date TIMESTAMP DEFAULT CURRENT_TIMESTAMP
141
+ )
142
+ """
143
+ )
144
+
145
+ # Insert test data
146
+ client.execute(
147
+ """
148
+ INSERT INTO test_users (id, name, email, age, department, salary) VALUES
149
+ (1, 'Alice Johnson', 'alice@example.com', 28, 'Engineering', 75000.00),
150
+ (2, 'Bob Smith', 'bob@example.com', 32, 'Marketing', 65000.00),
151
+ (3, 'Charlie Brown', 'charlie@example.com', 25, 'Engineering', 70000.00),
152
+ (4, 'Diana Prince', 'diana@example.com', 30, 'Sales', 60000.00),
153
+ (5, 'Eve Wilson', 'eve@example.com', 27, 'Engineering', 80000.00)
154
+ """
155
+ )
156
+
157
+ client.execute(
158
+ """
159
+ INSERT INTO test_products (id, name, price, category, stock) VALUES
160
+ (1, 'Laptop Pro', 1299.99, 'Electronics', 50),
161
+ (2, 'Wireless Mouse', 29.99, 'Electronics', 200),
162
+ (3, 'Office Chair', 199.99, 'Furniture', 30),
163
+ (4, 'Coffee Mug', 12.99, 'Accessories', 100),
164
+ (5, 'Monitor 4K', 399.99, 'Electronics', 25)
165
+ """
166
+ )
167
+
168
+ client.execute(
169
+ """
170
+ INSERT INTO test_orders (id, user_id, product_id, quantity, total_amount) VALUES
171
+ (1, 1, 1, 1, 1299.99),
172
+ (2, 2, 2, 2, 59.98),
173
+ (3, 3, 3, 1, 199.99),
174
+ (4, 1, 4, 3, 38.97),
175
+ (5, 4, 5, 1, 399.99)
176
+ """
177
+ )
178
+
179
+ yield
180
+
181
+ # Cleanup
182
+ try:
183
+ client.execute("DROP TABLE test_orders")
184
+ client.execute("DROP TABLE test_products")
185
+ client.execute("DROP TABLE test_users")
186
+ except:
187
+ pass
188
+
189
+ def test_basic_query_all(self, client, test_data_setup):
190
+ """Test basic query all functionality"""
191
+ users = client.query(User).all()
192
+ assert len(users) == 5
193
+ assert users[0].name == 'Alice Johnson'
194
+ assert users[0].email == 'alice@example.com'
195
+
196
+ def test_filter_by(self, client, test_data_setup):
197
+ """Test filter_by functionality"""
198
+ # Test single filter
199
+ engineering_users = client.query(User).filter_by(department='Engineering').all()
200
+ assert len(engineering_users) == 3
201
+ assert all(user.department == 'Engineering' for user in engineering_users)
202
+
203
+ # Test multiple filters
204
+ young_engineers = client.query(User).filter_by(department='Engineering', age=25).all()
205
+ assert len(young_engineers) == 1
206
+ assert young_engineers[0].name == 'Charlie Brown'
207
+
208
+ def test_filter(self, client, test_data_setup):
209
+ """Test filter functionality with custom conditions"""
210
+ # Test numeric comparison
211
+ high_salary_users = client.query(User).filter("salary > ?", 70000.0).all()
212
+ assert len(high_salary_users) == 2
213
+ assert all(user.salary > 70000.0 for user in high_salary_users)
214
+
215
+ # Test string comparison
216
+ electronics_products = client.query(Product).filter("category = ?", 'Electronics').all()
217
+ assert len(electronics_products) == 3
218
+ assert all(product.category == 'Electronics' for product in electronics_products)
219
+
220
+ def test_order_by(self, client, test_data_setup):
221
+ """Test order_by functionality"""
222
+ # Test ascending order
223
+ users_by_age = client.query(User).order_by('age').all()
224
+ assert users_by_age[0].age == 25
225
+ assert users_by_age[-1].age == 32
226
+
227
+ # Test descending order
228
+ users_by_salary_desc = client.query(User).order_by(desc('salary')).all()
229
+ assert users_by_salary_desc[0].salary == 80000.0
230
+ assert users_by_salary_desc[-1].salary == 60000.0
231
+
232
+ def test_limit(self, client, test_data_setup):
233
+ """Test limit functionality"""
234
+ top_users = client.query(User).order_by(desc('salary')).limit(3).all()
235
+ assert len(top_users) == 3
236
+ assert top_users[0].salary == 80000.0
237
+
238
+ def test_offset(self, client, test_data_setup):
239
+ """Test offset functionality"""
240
+ # MatrixOne requires LIMIT with OFFSET, so we use a large limit
241
+ users_skip_first = client.query(User).order_by('id').limit(100).offset(2).all()
242
+ assert len(users_skip_first) == 3
243
+ assert users_skip_first[0].id == 3
244
+
245
+ def test_limit_offset_combination(self, client, test_data_setup):
246
+ """Test limit and offset combination"""
247
+ users_page2 = client.query(User).order_by('id').limit(2).offset(2).all()
248
+ assert len(users_page2) == 2
249
+ assert users_page2[0].id == 3
250
+ assert users_page2[1].id == 4
251
+
252
+ def test_count(self, client, test_data_setup):
253
+ """Test count functionality"""
254
+ total_users = client.query(User).count()
255
+ assert total_users == 5
256
+
257
+ engineering_count = client.query(User).filter_by(department='Engineering').count()
258
+ assert engineering_count == 3
259
+
260
+ def test_first(self, client, test_data_setup):
261
+ """Test first functionality"""
262
+ first_user = client.query(User).order_by('id').first()
263
+ assert first_user is not None
264
+ assert first_user.id == 1
265
+ assert first_user.name == 'Alice Johnson'
266
+
267
+ # Test first with filter
268
+ engineering_user = client.query(User).filter_by(department='Engineering').first()
269
+ assert engineering_user is not None
270
+ assert engineering_user.department == 'Engineering'
271
+
272
+ def test_complex_queries(self, client, test_data_setup):
273
+ """Test complex query combinations"""
274
+ # Complex query: high-salary engineering users ordered by age
275
+ result = client.query(User).filter_by(department='Engineering').filter("salary > ?", 70000.0).order_by('age').all()
276
+
277
+ assert len(result) == 2
278
+ assert result[0].name == 'Eve Wilson' # age 27 (younger of the two)
279
+ assert result[1].name == 'Alice Johnson' # age 28 (older of the two)
280
+
281
+ def test_model_attributes(self, client, test_data_setup):
282
+ """Test model attribute access"""
283
+ user = client.query(User).first()
284
+ assert hasattr(user, 'id')
285
+ assert hasattr(user, 'name')
286
+ assert hasattr(user, 'email')
287
+ assert hasattr(user, 'age')
288
+ assert hasattr(user, 'department')
289
+ assert hasattr(user, 'salary')
290
+ assert hasattr(user, 'created_at')
291
+
292
+ def test_empty_results(self, client, test_data_setup):
293
+ """Test queries that return empty results"""
294
+ # Non-existent department
295
+ empty_result = client.query(User).filter_by(department='NonExistent').all()
296
+ assert len(empty_result) == 0
297
+
298
+ # Non-existent user
299
+ empty_first = client.query(User).filter_by(department='NonExistent').first()
300
+ assert empty_first is None
301
+
302
+ # Count of non-existent records
303
+ empty_count = client.query(User).filter_by(department='NonExistent').count()
304
+ assert empty_count == 0
305
+
306
+
307
+ # Define a global AsyncUser class for all async tests
308
+ class AsyncUser(Base):
309
+ __tablename__ = "async_test_users"
310
+ id = Column(Integer, primary_key=True)
311
+ name = Column(String(100))
312
+ email = Column(String(100))
313
+ age = Column(Integer)
314
+ department = Column(String(50))
315
+ salary = Column(DECIMAL(10, 2))
316
+ created_at = Column(TIMESTAMP)
317
+
318
+
319
+ # Define a global TestModel class for edge case tests
320
+ class TempTable1(Base):
321
+ __tablename__ = "test_malformed_filter"
322
+ id = Column(Integer, primary_key=True)
323
+ name = Column(String(50))
324
+
325
+
326
+ class TestAsyncMatrixOneQueryORM:
327
+ """Test AsyncMatrixOneQuery ORM functionality"""
328
+
329
+ @pytest_asyncio.fixture(scope="function")
330
+ async def async_client(self):
331
+ """Create and connect async MatrixOne client."""
332
+ client = AsyncClient()
333
+ host, port, user, password, database = online_config.get_connection_params()
334
+ await client.connect(host=host, port=port, user=user, password=password, database=database)
335
+ yield client
336
+ if hasattr(client, 'disconnect'):
337
+ await client.disconnect()
338
+
339
+ @pytest_asyncio.fixture(scope="function")
340
+ async def async_test_data_setup(self, async_client):
341
+ """Set up test data for async tests"""
342
+ # Clean up any existing test data
343
+ try:
344
+ await async_client.execute("DROP TABLE IF EXISTS async_test_orders")
345
+ await async_client.execute("DROP TABLE IF EXISTS async_test_products")
346
+ await async_client.execute("DROP TABLE IF EXISTS async_test_users")
347
+ except:
348
+ pass
349
+
350
+ # Create test tables
351
+ await async_client.execute(
352
+ """
353
+ CREATE TABLE async_test_users (
354
+ id INT PRIMARY KEY,
355
+ name VARCHAR(100),
356
+ email VARCHAR(100),
357
+ age INT,
358
+ department VARCHAR(50),
359
+ salary DECIMAL(10,2),
360
+ created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
361
+ )
362
+ """
363
+ )
364
+
365
+ await async_client.execute(
366
+ """
367
+ CREATE TABLE async_test_products (
368
+ id INT PRIMARY KEY,
369
+ name VARCHAR(100),
370
+ price DECIMAL(10,2),
371
+ category VARCHAR(50),
372
+ stock INT,
373
+ created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
374
+ )
375
+ """
376
+ )
377
+
378
+ # Insert test data
379
+ await async_client.execute(
380
+ """
381
+ INSERT INTO async_test_users (id, name, email, age, department, salary) VALUES
382
+ (1, 'Async Alice', 'async.alice@example.com', 28, 'Engineering', 75000.00),
383
+ (2, 'Async Bob', 'async.bob@example.com', 32, 'Marketing', 65000.00),
384
+ (3, 'Async Charlie', 'async.charlie@example.com', 25, 'Engineering', 70000.00),
385
+ (4, 'Async Diana', 'async.diana@example.com', 30, 'Sales', 60000.00),
386
+ (5, 'Async Eve', 'async.eve@example.com', 27, 'Engineering', 80000.00)
387
+ """
388
+ )
389
+
390
+ await async_client.execute(
391
+ """
392
+ INSERT INTO async_test_products (id, name, price, category, stock) VALUES
393
+ (1, 'Async Laptop', 1299.99, 'Electronics', 50),
394
+ (2, 'Async Mouse', 29.99, 'Electronics', 200),
395
+ (3, 'Async Chair', 199.99, 'Furniture', 30),
396
+ (4, 'Async Mug', 12.99, 'Accessories', 100),
397
+ (5, 'Async Monitor', 399.99, 'Electronics', 25)
398
+ """
399
+ )
400
+
401
+ yield
402
+
403
+ # Cleanup
404
+ try:
405
+ await async_client.execute("DROP TABLE async_test_products")
406
+ await async_client.execute("DROP TABLE async_test_users")
407
+ except:
408
+ pass
409
+
410
+ @pytest.mark.asyncio
411
+ async def test_async_basic_query_all(self, async_client, async_test_data_setup):
412
+ """Test async basic query all functionality"""
413
+ users = await async_client.query(AsyncUser).all()
414
+ assert len(users) == 5
415
+ assert users[0].name == 'Async Alice'
416
+
417
+ @pytest.mark.asyncio
418
+ async def test_async_filter_by(self, async_client, async_test_data_setup):
419
+ """Test async filter_by functionality"""
420
+ engineering_users = await async_client.query(AsyncUser).filter_by(department='Engineering').all()
421
+ assert len(engineering_users) == 3
422
+ assert all(user.department == 'Engineering' for user in engineering_users)
423
+
424
+ @pytest.mark.asyncio
425
+ async def test_async_filter(self, async_client, async_test_data_setup):
426
+ """Test async filter functionality"""
427
+ high_salary_users = await async_client.query(AsyncUser).filter("salary > ?", 70000.0).all()
428
+ assert len(high_salary_users) == 2
429
+ assert all(user.salary > 70000.0 for user in high_salary_users)
430
+
431
+ @pytest.mark.asyncio
432
+ async def test_async_order_by(self, async_client, async_test_data_setup):
433
+ """Test async order_by functionality"""
434
+ users_by_salary_desc = await async_client.query(AsyncUser).order_by(desc('salary')).all()
435
+ assert users_by_salary_desc[0].salary == 80000.0
436
+ assert users_by_salary_desc[-1].salary == 60000.0
437
+
438
+ @pytest.mark.asyncio
439
+ async def test_async_limit_offset(self, async_client, async_test_data_setup):
440
+ """Test async limit and offset functionality"""
441
+ top_users = await async_client.query(AsyncUser).order_by(desc('salary')).limit(3).all()
442
+ assert len(top_users) == 3
443
+ assert top_users[0].salary == 80000.0
444
+
445
+ users_skip_first = await async_client.query(AsyncUser).order_by('id').limit(100).offset(2).all()
446
+ assert len(users_skip_first) == 3
447
+ assert users_skip_first[0].id == 3
448
+
449
+ @pytest.mark.asyncio
450
+ async def test_async_count(self, async_client, async_test_data_setup):
451
+ """Test async count functionality"""
452
+ total_users = await async_client.query(AsyncUser).count()
453
+ assert total_users == 5
454
+
455
+ engineering_count = await async_client.query(AsyncUser).filter_by(department='Engineering').count()
456
+ assert engineering_count == 3
457
+
458
+ @pytest.mark.asyncio
459
+ async def test_async_first(self, async_client, async_test_data_setup):
460
+ """Test async first functionality"""
461
+ first_user = await async_client.query(AsyncUser).order_by('id').first()
462
+ assert first_user is not None
463
+ assert first_user.id == 1
464
+ assert first_user.name == 'Async Alice'
465
+
466
+ @pytest.mark.asyncio
467
+ async def test_async_complex_queries(self, async_client, async_test_data_setup):
468
+ """Test async complex query combinations"""
469
+ result = (
470
+ await async_client.query(AsyncUser)
471
+ .filter_by(department='Engineering')
472
+ .filter("salary > ?", 70000.0)
473
+ .order_by('age')
474
+ .all()
475
+ )
476
+
477
+ assert len(result) == 2
478
+ assert result[0].name == 'Async Eve' # age 27 (younger of the two)
479
+ assert result[1].name == 'Async Alice' # age 28 (older of the two)
480
+
481
+ @pytest.mark.asyncio
482
+ async def test_async_empty_results(self, async_client, async_test_data_setup):
483
+ """Test async queries that return empty results"""
484
+ # Non-existent department
485
+ empty_result = await async_client.query(AsyncUser).filter_by(department='NonExistent').all()
486
+ assert len(empty_result) == 0
487
+
488
+ # Non-existent user
489
+ empty_first = await async_client.query(AsyncUser).filter_by(department='NonExistent').first()
490
+ assert empty_first is None
491
+
492
+ # Count of non-existent records
493
+ empty_count = await async_client.query(AsyncUser).filter_by(department='NonExistent').count()
494
+ assert empty_count == 0
495
+
496
+
497
+ class TestMatrixOneQueryEdgeCases:
498
+ """Test edge cases and error conditions"""
499
+
500
+ @pytest.fixture(scope="class")
501
+ def client(self):
502
+ """Create and connect MatrixOne client."""
503
+ client = Client()
504
+ host, port, user, password, database = online_config.get_connection_params()
505
+ client.connect(host=host, port=port, user=user, password=password, database=database)
506
+ yield client
507
+ if hasattr(client, 'disconnect'):
508
+ client.disconnect()
509
+
510
+ def test_invalid_model(self, client):
511
+ """Test query with invalid model"""
512
+
513
+ class InvalidModel(Base):
514
+ __tablename__ = "non_existent_table"
515
+ id = Column(Integer, primary_key=True)
516
+
517
+ with pytest.raises(Exception):
518
+ client.query(InvalidModel).all()
519
+
520
+ def test_malformed_filter(self, client):
521
+ """Test malformed filter conditions"""
522
+ # Create a simple test table first
523
+ try:
524
+ client.execute("DROP TABLE IF EXISTS test_malformed")
525
+ client.execute("CREATE TABLE test_malformed (id INT PRIMARY KEY, name VARCHAR(50))")
526
+ client.execute("INSERT INTO test_malformed (id, name) VALUES (1, 'test')")
527
+
528
+ # This should raise an exception due to invalid SQL syntax
529
+ with pytest.raises(Exception):
530
+ client.query(TempTable1).filter("invalid_syntax").all()
531
+
532
+ finally:
533
+ try:
534
+ client.execute("DROP TABLE test_malformed")
535
+ except:
536
+ pass
537
+
538
+
539
+ if __name__ == "__main__":
540
+ pytest.main([__file__])