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,269 @@
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 CTE (Common Table Expression) integration with MatrixOneQuery
17
+
18
+ This test verifies that the new SQLAlchemy-style CTE support works correctly
19
+ with MatrixOneQuery, including:
20
+ - Creating CTEs from queries
21
+ - Using CTEs in other queries
22
+ - Multiple CTEs in a single query
23
+ - CTE parameter handling
24
+ """
25
+
26
+ import pytest
27
+ from sqlalchemy import Column, Integer, String, create_engine, text
28
+ from sqlalchemy.orm import sessionmaker
29
+
30
+ from matrixone.client import Client
31
+ from matrixone.orm import CTE, declarative_base
32
+
33
+ Base = declarative_base()
34
+
35
+
36
+ class User(Base):
37
+ __tablename__ = "cte_users"
38
+ id = Column(Integer, primary_key=True)
39
+ name = Column(String(50))
40
+ department = Column(String(50))
41
+ salary = Column(Integer)
42
+
43
+
44
+ class Article(Base):
45
+ __tablename__ = "cte_articles"
46
+ id = Column(Integer, primary_key=True)
47
+ title = Column(String(100))
48
+ author_id = Column(Integer)
49
+ views = Column(Integer)
50
+
51
+
52
+ class TestORMCTEIntegration:
53
+ """Test CTE integration with MatrixOneQuery"""
54
+
55
+ @pytest.fixture(autouse=True)
56
+ def setup_and_cleanup(self, connection_params, test_database):
57
+ """Setup test database and tables"""
58
+ host, port, user, password, database = connection_params
59
+ self.client = Client(host=host, port=port, user=user, password=password, database=database)
60
+ self.test_db = f"cte_test_db_{test_database}"
61
+
62
+ # Create test database
63
+ self.client.execute(f"CREATE DATABASE IF NOT EXISTS {self.test_db}")
64
+ self.client.execute(f"USE {self.test_db}")
65
+
66
+ # Create tables
67
+ self.client.execute(
68
+ f"""
69
+ CREATE TABLE IF NOT EXISTS {self.test_db}.cte_users (
70
+ id INT PRIMARY KEY AUTO_INCREMENT,
71
+ name VARCHAR(50),
72
+ department VARCHAR(50),
73
+ salary INT
74
+ )
75
+ """
76
+ )
77
+
78
+ self.client.execute(
79
+ f"""
80
+ CREATE TABLE IF NOT EXISTS {self.test_db}.cte_articles (
81
+ id INT PRIMARY KEY AUTO_INCREMENT,
82
+ title VARCHAR(100),
83
+ author_id INT,
84
+ views INT
85
+ )
86
+ """
87
+ )
88
+
89
+ # Insert test data
90
+ self.client.execute(
91
+ f"""
92
+ INSERT INTO {self.test_db}.cte_users (name, department, salary) VALUES
93
+ ('Alice', 'Engineering', 80000),
94
+ ('Bob', 'Engineering', 90000),
95
+ ('Charlie', 'Marketing', 70000),
96
+ ('Diana', 'Marketing', 75000),
97
+ ('Eve', 'Sales', 60000)
98
+ """
99
+ )
100
+
101
+ self.client.execute(
102
+ f"""
103
+ INSERT INTO {self.test_db}.cte_articles (title, author_id, views) VALUES
104
+ ('Python Tutorial', 1, 1000),
105
+ ('Database Design', 1, 800),
106
+ ('Marketing Strategy', 3, 600),
107
+ ('Sales Tips', 5, 400),
108
+ ('Advanced Python', 2, 1200)
109
+ """
110
+ )
111
+
112
+ yield
113
+
114
+ # Cleanup
115
+ try:
116
+ self.client.execute(f"DROP DATABASE IF EXISTS {self.test_db}")
117
+ except:
118
+ pass
119
+ # Client doesn't have close method, it's automatically managed
120
+
121
+ def test_basic_cte_creation(self):
122
+ """Test basic CTE creation from a query"""
123
+ # Create a CTE for high-salary users
124
+ high_salary_users = self.client.query(User).filter(User.salary > 75000).cte("high_salary_users")
125
+
126
+ # Verify CTE object properties
127
+ assert isinstance(high_salary_users, CTE)
128
+ assert high_salary_users.name == "high_salary_users"
129
+ assert high_salary_users.recursive == False
130
+
131
+ # Get SQL from CTE
132
+ cte_sql, cte_params = high_salary_users.as_sql()
133
+ assert "SELECT" in cte_sql
134
+ assert "cte_users" in cte_sql
135
+ assert "salary > 75000" in cte_sql
136
+
137
+ def test_cte_usage_in_query(self):
138
+ """Test using a CTE in another query"""
139
+ # Create CTE for engineering users
140
+ engineering_users = self.client.query(User).filter(User.department == "Engineering").cte("engineering_users")
141
+
142
+ # Use CTE in another query
143
+ result = self.client.query(engineering_users).all()
144
+
145
+ # Should return 2 users (Alice and Bob)
146
+ assert len(result) == 2
147
+ # Check that we got engineering users (Alice and Bob)
148
+ # Since we're querying from CTE, we get RowData objects with '*' attribute
149
+ # The actual data is in _values tuple: (id, name, department, salary)
150
+ names = [row._values[1] for row in result] # name is at index 1
151
+ assert "Alice" in names
152
+ assert "Bob" in names
153
+
154
+ def test_multiple_ctes_in_query(self):
155
+ """Test using multiple CTEs in a single query"""
156
+ # Create first CTE: high-salary users
157
+ high_salary = self.client.query(User).filter(User.salary > 70000).cte("high_salary")
158
+
159
+ # Create second CTE: popular articles
160
+ popular_articles = self.client.query(Article).filter(Article.views > 500).cte("popular_articles")
161
+
162
+ # Use both CTEs in a query with with_cte method
163
+ result = (
164
+ self.client.query(high_salary)
165
+ .with_cte(popular_articles)
166
+ .join(popular_articles, onclause="high_salary.id = popular_articles.author_id")
167
+ .all()
168
+ )
169
+
170
+ # Should return users who have popular articles and high salary
171
+ assert len(result) >= 0 # At least Alice (high salary + popular articles)
172
+
173
+ def test_cte_with_aggregation(self):
174
+ """Test CTE with aggregation functions"""
175
+ # Create CTE with department statistics
176
+ dept_stats = self.client.query(User.department, User.salary).cte("dept_stats")
177
+
178
+ # Use CTE in aggregation query
179
+ result = self.client.query(dept_stats).all()
180
+
181
+ # Should return all department-salary combinations
182
+ assert len(result) == 5 # 5 users total
183
+
184
+ def test_recursive_cte_flag(self):
185
+ """Test recursive CTE flag"""
186
+ # Create a recursive CTE (even though we won't use recursion in this test)
187
+ recursive_cte = self.client.query(User).filter(User.id == 1).cte("recursive_users", recursive=True)
188
+
189
+ assert recursive_cte.recursive == True
190
+ assert recursive_cte.name == "recursive_users"
191
+
192
+ def test_cte_in_transaction(self):
193
+ """Test CTE usage within a transaction"""
194
+ with self.client.transaction() as tx:
195
+ # Create CTE within transaction
196
+ tx_users = tx.query(User).filter(User.department == "Marketing").cte("tx_users")
197
+
198
+ # Use CTE within transaction
199
+ result = tx.query(tx_users).all()
200
+
201
+ # Should return 2 marketing users
202
+ assert len(result) == 2
203
+ names = [row._values[1] for row in result] # name is at index 1
204
+ assert all(name in ["Charlie", "Diana"] for name in names)
205
+
206
+ def test_cte_with_join(self):
207
+ """Test CTE with JOIN operations"""
208
+ # Create CTE for users with articles - select specific columns to avoid ambiguity
209
+ users_with_articles = (
210
+ self.client.query(User.id, User.name, User.department, User.salary)
211
+ .join("cte_articles", onclause="cte_users.id = cte_articles.author_id")
212
+ .cte("users_with_articles")
213
+ )
214
+
215
+ # Query from the CTE
216
+ result = self.client.query(users_with_articles).all()
217
+
218
+ # Should return users who have written articles
219
+ assert len(result) >= 0
220
+ # All returned users should have written at least one article
221
+
222
+ def test_cte_sql_generation(self):
223
+ """Test that CTE generates correct SQL"""
224
+ # Create a simple CTE
225
+ simple_cte = self.client.query(User.id, User.name).filter(User.salary > 80000).cte("simple_cte")
226
+
227
+ # Get the SQL
228
+ cte_sql, params = simple_cte.as_sql()
229
+
230
+ # Verify SQL structure
231
+ assert "SELECT" in cte_sql
232
+ assert "cte_users" in cte_sql
233
+ assert "id" in cte_sql
234
+ assert "name" in cte_sql
235
+ assert "salary > 80000" in cte_sql
236
+
237
+ # Use CTE in a query and check final SQL
238
+ query = self.client.query(simple_cte)
239
+ final_sql, final_params = query._build_sql()
240
+
241
+ # Should have WITH clause
242
+ assert "WITH simple_cte AS" in final_sql
243
+ assert "SELECT * FROM simple_cte" in final_sql
244
+
245
+ def test_cte_parameter_handling(self):
246
+ """Test that CTE parameters are handled correctly"""
247
+ # Create CTE with parameterized query
248
+ param_cte = self.client.query(User).filter(User.salary > 75000).cte("param_cte") # This should generate a parameter
249
+
250
+ # Use CTE in query
251
+ result = self.client.query(param_cte).all()
252
+
253
+ # Should return high-salary users
254
+ assert len(result) == 2 # Alice and Bob
255
+ salaries = [row._values[3] for row in result] # salary is at index 3
256
+ assert all(salary > 75000 for salary in salaries)
257
+
258
+ def test_cte_with_raw_sql(self):
259
+ """Test CTE creation with raw SQL string"""
260
+ # Create CTE with raw SQL
261
+ raw_cte = CTE("raw_cte", f"SELECT * FROM {self.test_db}.cte_users WHERE salary > 80000")
262
+
263
+ # Use in query
264
+ result = self.client.query(raw_cte).all()
265
+
266
+ # Should return high-salary users
267
+ assert len(result) == 1 # Only Bob
268
+ assert result[0]._values[1] == "Bob" # name is at index 1
269
+ assert result[0]._values[3] > 80000 # salary is at index 3
@@ -0,0 +1,270 @@
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 ORM functionality - tests actual database operations
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 sqlalchemy import Column, Integer, String, DECIMAL
29
+
30
+ # SQLAlchemy compatibility import
31
+ try:
32
+ from sqlalchemy.orm import declarative_base
33
+ except ImportError:
34
+ from matrixone.orm import declarative_base
35
+
36
+ Base = declarative_base()
37
+ from matrixone.exceptions import QueryError
38
+ from .test_config import online_config
39
+
40
+
41
+ class User(Base):
42
+ """Test user model"""
43
+
44
+ __tablename__ = "test_users"
45
+
46
+ id = Column(Integer, primary_key=True)
47
+ name = Column(String(100))
48
+ email = Column(String(100))
49
+ age = Column(Integer)
50
+
51
+
52
+ class Product(Base):
53
+ """Test product model"""
54
+
55
+ __tablename__ = "test_products"
56
+
57
+ id = Column(Integer, primary_key=True)
58
+ name = Column(String(100))
59
+ price = Column(DECIMAL(10, 2))
60
+ category = Column(String(50))
61
+
62
+
63
+ class TestORMOnline:
64
+ """Online tests for ORM functionality"""
65
+
66
+ @pytest.fixture(scope="class")
67
+ def test_client(self):
68
+ """Create and connect Client for testing"""
69
+ host, port, user, password, database = online_config.get_connection_params()
70
+ client = Client()
71
+ client.connect(host=host, port=port, user=user, password=password, database=database)
72
+ try:
73
+ yield client
74
+ finally:
75
+ try:
76
+ client.disconnect()
77
+ except Exception as e:
78
+ print(f"Warning: Failed to disconnect client: {e}")
79
+
80
+ @pytest.fixture(scope="class")
81
+ def test_database(self, test_client):
82
+ """Set up test database and tables"""
83
+ test_db = "test_orm_db"
84
+
85
+ try:
86
+ test_client.execute(f"CREATE DATABASE IF NOT EXISTS {test_db}")
87
+ test_client.execute(f"USE {test_db}")
88
+
89
+ # Create test tables using SQLAlchemy models
90
+ Base.metadata.create_all(test_client._engine)
91
+
92
+ # Insert test data
93
+ test_client.execute(
94
+ """
95
+ INSERT INTO test_users VALUES
96
+ (1, 'John Doe', 'john@example.com', 30),
97
+ (2, 'Jane Smith', 'jane@example.com', 25),
98
+ (3, 'Bob Johnson', 'bob@example.com', 35)
99
+ """
100
+ )
101
+
102
+ test_client.execute(
103
+ """
104
+ INSERT INTO test_products VALUES
105
+ (1, 'Laptop', 999.99, 'Electronics'),
106
+ (2, 'Book', 19.99, 'Education'),
107
+ (3, 'Phone', 699.99, 'Electronics')
108
+ """
109
+ )
110
+
111
+ yield test_db
112
+
113
+ finally:
114
+ # Clean up
115
+ try:
116
+ test_client.execute(f"DROP DATABASE IF EXISTS {test_db}")
117
+ except Exception as e:
118
+ print(f"Cleanup failed: {e}")
119
+
120
+ def test_model_creation_and_attributes(self):
121
+ """Test model creation and attribute access"""
122
+ user = User(id=1, name="Test User", email="test@example.com", age=30)
123
+
124
+ assert user.id == 1
125
+ assert user.name == "Test User"
126
+ assert user.email == "test@example.com"
127
+ assert user.age == 30
128
+
129
+ # Test attribute access (SQLAlchemy models don't have to_dict by default)
130
+ assert hasattr(user, 'id')
131
+ assert hasattr(user, 'name')
132
+ assert hasattr(user, 'email')
133
+ assert hasattr(user, 'age')
134
+
135
+ def test_model_table_creation(self):
136
+ """Test creating tables from models"""
137
+ # This test verifies that the model metadata is correctly set up
138
+ assert User.__tablename__ == "test_users"
139
+ assert Product.__tablename__ == "test_products"
140
+
141
+ # Check that columns are properly defined
142
+ assert hasattr(User, '__table__')
143
+ assert 'id' in [col.name for col in User.__table__.columns]
144
+ assert 'name' in [col.name for col in User.__table__.columns]
145
+ assert 'email' in [col.name for col in User.__table__.columns]
146
+ assert 'age' in [col.name for col in User.__table__.columns]
147
+
148
+ def test_orm_query_basic_operations(self, test_client, test_database):
149
+ """Test basic ORM query operations"""
150
+ # Test all() method
151
+ users = test_client.query(User).all()
152
+ assert len(users) == 3
153
+
154
+ # Test first() method
155
+ first_user = test_client.query(User).first()
156
+ assert first_user is not None
157
+ assert isinstance(first_user, User)
158
+
159
+ # Test filter_by() method
160
+ john = test_client.query(User).filter_by(name="John Doe").first()
161
+ assert john is not None
162
+ assert john.name == "John Doe"
163
+ assert john.email == "john@example.com"
164
+
165
+ def test_orm_query_filtering(self, test_client, test_database):
166
+ """Test ORM query filtering"""
167
+ # Test filter_by with multiple conditions
168
+ electronics = test_client.query(Product).filter_by(category="Electronics").all()
169
+ assert len(electronics) == 2
170
+
171
+ # Test filter() method with conditions
172
+ expensive_products = test_client.query(Product).filter("price > ?", 500).all()
173
+ assert len(expensive_products) == 2
174
+
175
+ # Test combined filtering
176
+ expensive_electronics = test_client.query(Product).filter_by(category="Electronics").filter("price > ?", 500).all()
177
+ assert len(expensive_electronics) == 2
178
+
179
+ def test_orm_query_counting(self, test_client, test_database):
180
+ """Test ORM query counting"""
181
+ # Test count() method
182
+ total_users = test_client.query(User).count()
183
+ assert total_users == 3
184
+
185
+ # Test count() with filter
186
+ electronics_count = test_client.query(Product).filter_by(category="Electronics").count()
187
+ assert electronics_count == 2
188
+
189
+ # Test count() with complex filter
190
+ expensive_count = test_client.query(Product).filter("price > ?", 500).count()
191
+ assert expensive_count == 2
192
+
193
+ def test_orm_query_ordering(self, test_client, test_database):
194
+ """Test ORM query ordering"""
195
+ from matrixone.orm import desc
196
+
197
+ # Test order_by ascending (default)
198
+ users_asc = test_client.query(User).order_by("age").all()
199
+ assert len(users_asc) == 3
200
+ assert users_asc[0].age <= users_asc[1].age <= users_asc[2].age
201
+
202
+ # Test order_by descending
203
+ users_desc = test_client.query(User).order_by(desc("age")).all()
204
+ assert len(users_desc) == 3
205
+ assert users_desc[0].age >= users_desc[1].age >= users_desc[2].age
206
+
207
+ def test_orm_query_limiting(self, test_client, test_database):
208
+ """Test ORM query limiting"""
209
+ # Test limit()
210
+ limited_users = test_client.query(User).limit(2).all()
211
+ assert len(limited_users) == 2
212
+
213
+ # Test limit() with order_by
214
+ top_users = test_client.query(User).order_by("age").limit(1).all()
215
+ assert len(top_users) == 1
216
+ assert top_users[0].age == 25 # Youngest user
217
+
218
+ def test_orm_model_instantiation(self, test_client, test_database):
219
+ """Test model instantiation from query results"""
220
+ # Get a user from database
221
+ user = test_client.query(User).filter_by(name="John Doe").first()
222
+ assert user is not None
223
+ assert isinstance(user, User)
224
+ assert user.id == 1
225
+ assert user.name == "John Doe"
226
+ assert user.email == "john@example.com"
227
+ assert user.age == 30
228
+
229
+ # Test that we can access all attributes
230
+ assert hasattr(user, 'id')
231
+ assert hasattr(user, 'name')
232
+ assert hasattr(user, 'email')
233
+ assert hasattr(user, 'age')
234
+
235
+ def test_orm_error_handling(self, test_client, test_database):
236
+ """Test ORM error handling"""
237
+ # Test querying non-existent table
238
+ with pytest.raises(QueryError):
239
+ test_client.query(User).filter("invalid_column = ?", "value").all()
240
+
241
+ # Test invalid filter syntax
242
+ with pytest.raises(QueryError):
243
+ test_client.query(User).filter("invalid_syntax").all()
244
+
245
+ def test_orm_complex_queries(self, test_client, test_database):
246
+ """Test complex ORM queries"""
247
+ # Test multiple filters
248
+ result = (
249
+ test_client.query(Product)
250
+ .filter_by(category="Electronics")
251
+ .filter("price > ?", 500)
252
+ .order_by("price")
253
+ .limit(1)
254
+ .all()
255
+ )
256
+
257
+ assert len(result) == 1
258
+ assert result[0].category == "Electronics"
259
+ assert result[0].price > 500
260
+
261
+ # Test chaining multiple operations
262
+ users = test_client.query(User).filter("age > ?", 25).order_by("age").limit(2).all()
263
+
264
+ assert len(users) == 2
265
+ assert all(user.age > 25 for user in users)
266
+ assert users[0].age <= users[1].age # Should be ordered by age
267
+
268
+
269
+ if __name__ == "__main__":
270
+ pytest.main([__file__])