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,346 @@
1
+ #!/usr/bin/env python3
2
+
3
+ # Copyright 2021 - 2022 Matrix Origin
4
+ #
5
+ # Licensed under the Apache License, Version 2.0 (the "License");
6
+ # you may not use this file except in compliance with the License.
7
+ # You may obtain a copy of the License at
8
+ #
9
+ # http://www.apache.org/licenses/LICENSE-2.0
10
+ #
11
+ # Unless required by applicable law or agreed to in writing, software
12
+ # distributed under the License is distributed on an "AS IS" BASIS,
13
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14
+ # See the License for the specific language governing permissions and
15
+ # limitations under the License.
16
+
17
+ """
18
+ Test: EXPLAIN, EXPLAIN ANALYZE, and to_sql() methods
19
+
20
+ This test verifies that the explain methods in MatrixOneQuery work correctly
21
+ for analyzing query execution plans and generating SQL statements.
22
+ """
23
+
24
+ import pytest
25
+ import sys
26
+ import os
27
+ from datetime import datetime
28
+
29
+ # Add the current directory to Python path
30
+ sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
31
+
32
+ from matrixone import Client
33
+ from sqlalchemy import Column, Integer, String, DateTime, func, desc, asc
34
+ from matrixone.orm import declarative_base
35
+
36
+ # Create SQLAlchemy models
37
+ Base = declarative_base()
38
+
39
+
40
+ class User(Base):
41
+ __tablename__ = 'users'
42
+ id = Column(Integer, primary_key=True)
43
+ name = Column(String(50))
44
+ age = Column(Integer)
45
+ department = Column(String(50))
46
+ salary = Column(Integer)
47
+ created_at = Column(DateTime)
48
+ active = Column(Integer, default=1) # Add active column for CTE test
49
+
50
+
51
+ class Product(Base):
52
+ __tablename__ = 'products'
53
+ id = Column(Integer, primary_key=True)
54
+ name = Column(String(100))
55
+ price = Column(Integer)
56
+ category = Column(String(50))
57
+ created_at = Column(DateTime)
58
+ user_id = Column(Integer) # Add user_id for CTE test
59
+
60
+
61
+ class Order(Base):
62
+ __tablename__ = 'orders'
63
+ id = Column(Integer, primary_key=True)
64
+ user_id = Column(Integer)
65
+ product_id = Column(Integer)
66
+ quantity = Column(Integer)
67
+ total_amount = Column(Integer)
68
+ created_at = Column(DateTime)
69
+
70
+
71
+ class TestExplainMethods:
72
+ """Test class for explain methods functionality"""
73
+
74
+ def setup_method(self):
75
+ """Set up test client"""
76
+ self.client = Client("test://localhost:6001/test")
77
+
78
+ def test_to_sql_basic_query(self):
79
+ """Test to_sql() method with basic query"""
80
+ query = self.client.query(User).filter(User.age > 25).order_by(User.name)
81
+ sql = query.to_sql()
82
+
83
+ assert "SELECT" in sql
84
+ assert "FROM users" in sql
85
+ assert "WHERE age > 25" in sql
86
+ assert "ORDER BY name" in sql
87
+
88
+ def test_to_sql_with_parameters(self):
89
+ """Test to_sql() method with parameters"""
90
+ query = self.client.query(User).filter("age > ?", 30).filter("department = ?", "Engineering")
91
+ sql = query.to_sql()
92
+
93
+ assert "WHERE age > 30" in sql
94
+ assert "department = 'Engineering'" in sql
95
+
96
+ def test_to_sql_complex_query(self):
97
+ """Test to_sql() method with complex query"""
98
+ query = (
99
+ self.client.query(User)
100
+ .select(User.department, func.count(User.id), func.avg(User.salary))
101
+ .filter(User.age > 25)
102
+ .group_by(User.department)
103
+ .having(func.count(User.id) > 1)
104
+ .order_by(func.avg(User.salary).desc())
105
+ .limit(10)
106
+ )
107
+
108
+ sql = query.to_sql()
109
+
110
+ assert "SELECT" in sql
111
+ assert "FROM users" in sql
112
+ assert "WHERE age > 25" in sql
113
+ assert "GROUP BY department" in sql
114
+ assert "HAVING count(id) > 1" in sql
115
+ assert "ORDER BY avg(salary) DESC" in sql
116
+ assert "LIMIT 10" in sql
117
+
118
+ def test_to_sql_with_joins(self):
119
+ """Test to_sql() method with joins"""
120
+ query = (
121
+ self.client.query(User)
122
+ .join(Order, User.id == Order.user_id)
123
+ .select(User.name, func.sum(Order.total_amount))
124
+ .filter(User.department == 'Sales')
125
+ .group_by(User.id, User.name)
126
+ .having(func.sum(Order.total_amount) > 10000)
127
+ .order_by(func.sum(Order.total_amount).desc())
128
+ )
129
+
130
+ sql = query.to_sql()
131
+
132
+ assert "JOIN orders" in sql
133
+ assert "WHERE department = 'Sales'" in sql
134
+ assert "GROUP BY id, name" in sql
135
+ assert "HAVING sum(total_amount) > 10000" in sql
136
+
137
+ def test_explain_sql_generation(self):
138
+ """Test explain() method SQL generation"""
139
+ query = self.client.query(User).filter(User.department == 'Engineering').order_by(User.salary.desc())
140
+ sql, params = query._build_sql()
141
+
142
+ # Test basic EXPLAIN
143
+ explain_sql = f"EXPLAIN {sql}"
144
+ assert explain_sql.startswith("EXPLAIN")
145
+ assert "SELECT" in explain_sql
146
+ assert "FROM users" in explain_sql
147
+ assert "WHERE department = 'Engineering'" in explain_sql
148
+ assert "ORDER BY salary DESC" in explain_sql
149
+
150
+ def test_explain_verbose_sql_generation(self):
151
+ """Test explain() method with verbose SQL generation"""
152
+ query = (
153
+ self.client.query(User)
154
+ .select(User.department, func.count(User.id), func.avg(User.salary))
155
+ .filter(User.age > 25)
156
+ .group_by(User.department)
157
+ .having(func.count(User.id) > 1)
158
+ )
159
+
160
+ sql, params = query._build_sql()
161
+
162
+ # Test EXPLAIN VERBOSE
163
+ explain_verbose_sql = f"EXPLAIN VERBOSE {sql}"
164
+ assert explain_verbose_sql.startswith("EXPLAIN VERBOSE")
165
+ assert "SELECT" in explain_verbose_sql
166
+ assert "GROUP BY department" in explain_verbose_sql
167
+ assert "HAVING count(id) > 1" in explain_verbose_sql
168
+
169
+ def test_explain_analyze_sql_generation(self):
170
+ """Test explain_analyze() method SQL generation"""
171
+ query = self.client.query(User).filter(User.salary > 70000).order_by(User.age.desc())
172
+ sql, params = query._build_sql()
173
+
174
+ # Test EXPLAIN ANALYZE
175
+ explain_analyze_sql = f"EXPLAIN ANALYZE {sql}"
176
+ assert explain_analyze_sql.startswith("EXPLAIN ANALYZE")
177
+ assert "SELECT" in explain_analyze_sql
178
+ assert "WHERE salary > 70000" in explain_analyze_sql
179
+ assert "ORDER BY age DESC" in explain_analyze_sql
180
+
181
+ def test_explain_analyze_verbose_sql_generation(self):
182
+ """Test explain_analyze() method with verbose SQL generation"""
183
+ query = (
184
+ self.client.query(User)
185
+ .join(Order, User.id == Order.user_id)
186
+ .select(User.name, func.sum(Order.total_amount))
187
+ .filter(User.department == 'Sales')
188
+ .group_by(User.id, User.name)
189
+ .having(func.sum(Order.total_amount) > 10000)
190
+ .order_by(func.sum(Order.total_amount).desc())
191
+ )
192
+
193
+ sql, params = query._build_sql()
194
+
195
+ # Test EXPLAIN ANALYZE VERBOSE
196
+ explain_analyze_verbose_sql = f"EXPLAIN ANALYZE VERBOSE {sql}"
197
+ assert explain_analyze_verbose_sql.startswith("EXPLAIN ANALYZE VERBOSE")
198
+ assert "SELECT" in explain_analyze_verbose_sql
199
+ assert "JOIN orders" in explain_analyze_verbose_sql
200
+ assert "GROUP BY id, name" in explain_analyze_verbose_sql
201
+
202
+ def test_explain_methods_with_cte(self):
203
+ """Test explain methods with CTE queries"""
204
+ # Use string-based CTE instead of SQLAlchemy CTE object
205
+ query = (
206
+ self.client.query(Product)
207
+ .select(Product.name, "user_stats.department")
208
+ .filter("user_id IN (SELECT id FROM users WHERE active = 1)")
209
+ )
210
+
211
+ sql = query.to_sql()
212
+
213
+ assert "SELECT" in sql
214
+ assert "FROM products" in sql
215
+ assert "WHERE user_id IN" in sql
216
+
217
+ # Test EXPLAIN with CTE-like query
218
+ explain_sql = f"EXPLAIN {sql}"
219
+ assert explain_sql.startswith("EXPLAIN")
220
+
221
+ def test_explain_methods_with_subquery(self):
222
+ """Test explain methods with subqueries"""
223
+ # Use string-based subquery instead of SQLAlchemy expression
224
+ subquery_sql = "SELECT id FROM users WHERE salary > 80000 ORDER BY salary DESC LIMIT 5"
225
+
226
+ query = (
227
+ self.client.query(User)
228
+ .select(User.name, User.salary)
229
+ .filter(f"id IN ({subquery_sql})")
230
+ .order_by(User.salary.desc())
231
+ )
232
+
233
+ sql = query.to_sql()
234
+
235
+ assert "SELECT" in sql
236
+ assert "WHERE id IN" in sql
237
+
238
+ # Test EXPLAIN ANALYZE with subquery
239
+ explain_analyze_sql = f"EXPLAIN ANALYZE {sql}"
240
+ assert explain_analyze_sql.startswith("EXPLAIN ANALYZE")
241
+
242
+ def test_explain_methods_parameter_handling(self):
243
+ """Test that explain methods handle parameters correctly"""
244
+ # Test with parameters
245
+ query = self.client.query(User).filter("age > ?", 25).filter("department = ?", "Engineering")
246
+ sql, params = query._build_sql()
247
+
248
+ # Test that the SQL is generated correctly
249
+ assert "SELECT" in sql
250
+ assert "FROM users" in sql
251
+ assert "age > 25" in sql
252
+ assert "department = 'Engineering'" in sql
253
+
254
+ # Test to_sql() method (should be the same as _build_sql() with substitution)
255
+ formatted_sql = query.to_sql()
256
+ assert "age > 25" in formatted_sql
257
+ assert "department = 'Engineering'" in formatted_sql
258
+ assert formatted_sql == sql # Should be identical
259
+
260
+ def test_explain_methods_edge_cases(self):
261
+ """Test explain methods with edge cases"""
262
+ # Empty query
263
+ query = self.client.query(User)
264
+ sql = query.to_sql()
265
+ assert "SELECT * FROM users" in sql
266
+
267
+ # Query with only LIMIT
268
+ query = self.client.query(User).limit(10)
269
+ sql = query.to_sql()
270
+ assert "LIMIT 10" in sql
271
+
272
+ # Query with only ORDER BY
273
+ query = self.client.query(User).order_by(User.name)
274
+ sql = query.to_sql()
275
+ assert "ORDER BY name" in sql
276
+
277
+ # Query with multiple ORDER BY columns
278
+ query = self.client.query(User).order_by(User.department, User.name.desc())
279
+ sql = query.to_sql()
280
+ assert "ORDER BY department, name DESC" in sql
281
+
282
+ def test_explain_methods_performance_queries(self):
283
+ """Test explain methods with performance-focused queries"""
284
+ # Complex aggregation query
285
+ query = (
286
+ self.client.query(User)
287
+ .select(
288
+ User.department,
289
+ func.count(User.id).label('user_count'),
290
+ func.avg(User.salary).label('avg_salary'),
291
+ func.max(User.salary).label('max_salary'),
292
+ func.min(User.salary).label('min_salary'),
293
+ )
294
+ .filter(User.age > 25)
295
+ .group_by(User.department)
296
+ .having(func.count(User.id) > 1)
297
+ .order_by(func.avg(User.salary).desc())
298
+ .limit(20)
299
+ )
300
+
301
+ sql = query.to_sql()
302
+
303
+ assert "count(users.id) AS user_count" in sql
304
+ assert "avg(users.salary) AS avg_salary" in sql
305
+ assert "max(users.salary) AS max_salary" in sql
306
+ assert "min(users.salary) AS min_salary" in sql
307
+ assert "GROUP BY department" in sql
308
+ assert "HAVING count(id) > 1" in sql
309
+ assert "ORDER BY avg(salary) DESC" in sql
310
+ assert "LIMIT 20" in sql
311
+
312
+ # Test EXPLAIN ANALYZE for performance analysis
313
+ explain_analyze_sql = f"EXPLAIN ANALYZE {sql}"
314
+ assert explain_analyze_sql.startswith("EXPLAIN ANALYZE")
315
+
316
+ def test_explain_methods_with_functions(self):
317
+ """Test explain methods with various SQL functions"""
318
+ query = (
319
+ self.client.query(User)
320
+ .select(
321
+ func.year(User.created_at).label('year'),
322
+ func.month(User.created_at).label('month'),
323
+ func.count(User.id).label('user_count'),
324
+ )
325
+ .filter(func.year(User.created_at) > 2020)
326
+ .group_by(func.year(User.created_at), func.month(User.created_at))
327
+ .order_by(func.year(User.created_at).desc(), func.month(User.created_at).desc())
328
+ )
329
+
330
+ sql = query.to_sql()
331
+
332
+ assert "year(users.created_at) AS year" in sql
333
+ assert "month(users.created_at) AS month" in sql
334
+ assert "count(users.id) AS user_count" in sql
335
+ assert "WHERE year(created_at) > 2020" in sql
336
+ assert "GROUP BY year(created_at), month(created_at)" in sql
337
+ assert "ORDER BY year(created_at) DESC, month(created_at) DESC" in sql
338
+
339
+ # Test EXPLAIN VERBOSE for function analysis
340
+ explain_verbose_sql = f"EXPLAIN VERBOSE {sql}"
341
+ assert explain_verbose_sql.startswith("EXPLAIN VERBOSE")
342
+
343
+
344
+ if __name__ == "__main__":
345
+ # Run tests if executed directly
346
+ pytest.main([__file__, "-v"])
@@ -0,0 +1,237 @@
1
+ #!/usr/bin/env python3
2
+
3
+ # Copyright 2021 - 2022 Matrix Origin
4
+ #
5
+ # Licensed under the Apache License, Version 2.0 (the "License");
6
+ # you may not use this file except in compliance with the License.
7
+ # You may obtain a copy of the License at
8
+ #
9
+ # http://www.apache.org/licenses/LICENSE-2.0
10
+ #
11
+ # Unless required by applicable law or agreed to in writing, software
12
+ # distributed under the License is distributed on an "AS IS" BASIS,
13
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14
+ # See the License for the specific language governing permissions and
15
+ # limitations under the License.
16
+
17
+ """
18
+ Test: filter(logical_in(...)) syntax for enhanced IN functionality
19
+
20
+ This test verifies that the filter(logical_in(...)) syntax works correctly with various
21
+ value types including FulltextFilter objects, lists, and SQLAlchemy expressions.
22
+ """
23
+
24
+ import pytest
25
+ import sys
26
+ import os
27
+ from datetime import datetime
28
+
29
+ # Add the current directory to Python path
30
+ sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
31
+
32
+ from matrixone import Client
33
+ from sqlalchemy import Column, Integer, String, DateTime, func
34
+ from matrixone.orm import declarative_base, logical_in
35
+ from matrixone.sqlalchemy_ext.fulltext_search import boolean_match
36
+
37
+ # Create SQLAlchemy models
38
+ Base = declarative_base()
39
+
40
+
41
+ class User(Base):
42
+ __tablename__ = 'users'
43
+ id = Column(Integer, primary_key=True)
44
+ name = Column(String(50))
45
+ age = Column(Integer)
46
+ city = Column(String(50))
47
+ department = Column(String(50))
48
+ salary = Column(Integer)
49
+ created_at = Column(DateTime)
50
+
51
+
52
+ class Article(Base):
53
+ __tablename__ = 'articles'
54
+ id = Column(Integer, primary_key=True)
55
+ title = Column(String(100))
56
+ content = Column(String(500))
57
+ author = Column(String(50))
58
+ created_at = Column(DateTime)
59
+
60
+
61
+ class TestFilterLogicalIn:
62
+ """Test class for filter(logical_in(...)) syntax functionality"""
63
+
64
+ def setup_method(self):
65
+ """Set up test client"""
66
+ self.client = Client("test://localhost:6001/test")
67
+
68
+ def test_filter_logical_in_with_list_values(self):
69
+ """Test filter(logical_in(...)) with list of values"""
70
+ # Test with integer list
71
+ query1 = self.client.query(User).filter(logical_in(User.id, [1, 2, 3, 4]))
72
+ sql1 = query1.to_sql()
73
+
74
+ assert "SELECT" in sql1
75
+ assert "FROM users" in sql1
76
+ assert "WHERE id IN (1,2,3,4)" in sql1
77
+
78
+ # Test with string list
79
+ query2 = self.client.query(User).filter(logical_in("city", ["北京", "上海", "广州"]))
80
+ sql2 = query2.to_sql()
81
+
82
+ assert "WHERE city IN ('北京','上海','广州')" in sql2
83
+
84
+ # Test with empty list
85
+ query3 = self.client.query(User).filter(logical_in(User.id, []))
86
+ sql3 = query3.to_sql()
87
+
88
+ assert "WHERE 1=0" in sql3 # Empty list should result in always false condition
89
+
90
+ def test_filter_logical_in_with_single_value(self):
91
+ """Test filter(logical_in(...)) with single value"""
92
+ query = self.client.query(User).filter(logical_in(User.id, 5))
93
+ sql = query.to_sql()
94
+
95
+ assert "WHERE id IN (5)" in sql
96
+
97
+ def test_filter_logical_in_with_fulltext_filter(self):
98
+ """Test filter(logical_in(...)) with FulltextFilter objects"""
99
+ # Basic boolean_match
100
+ query1 = self.client.query(Article).filter(logical_in(Article.id, boolean_match("title", "content").must("python")))
101
+ sql1 = query1.to_sql()
102
+
103
+ assert "SELECT" in sql1
104
+ assert "FROM articles" in sql1
105
+ assert "WHERE id IN (SELECT id FROM table WHERE MATCH(title, content) AGAINST('+python' IN BOOLEAN MODE))" in sql1
106
+
107
+ # Complex boolean_match
108
+ query2 = self.client.query(Article).filter(
109
+ logical_in(
110
+ Article.id, boolean_match("title", "content").must("python").encourage("programming").must_not("java")
111
+ )
112
+ )
113
+ sql2 = query2.to_sql()
114
+
115
+ assert (
116
+ "WHERE id IN (SELECT id FROM table WHERE MATCH(title, content) AGAINST('+python programming -java' IN BOOLEAN MODE))"
117
+ in sql2
118
+ )
119
+
120
+ def test_filter_logical_in_with_subquery(self):
121
+ """Test filter(logical_in(...)) with subquery objects"""
122
+ # Create a subquery
123
+ subquery = self.client.query(User).select(User.id).filter(User.age > 25)
124
+
125
+ query = self.client.query(Article).filter(logical_in(Article.author, subquery))
126
+ sql = query.to_sql()
127
+
128
+ assert "WHERE author IN (SELECT users.id AS id FROM users WHERE age > 25)" in sql
129
+
130
+ def test_filter_logical_in_with_sqlalchemy_expressions(self):
131
+ """Test filter(logical_in(...)) with SQLAlchemy expressions"""
132
+ # Test with function expression
133
+ query = self.client.query(User).filter(logical_in("id", func.count(User.id)))
134
+ sql = query.to_sql()
135
+
136
+ assert "WHERE id IN (count(id))" in sql
137
+
138
+ def test_filter_logical_in_with_string_column(self):
139
+ """Test filter(logical_in(...)) with string column names"""
140
+ query = self.client.query(User).filter(logical_in("department", ["Engineering", "Sales", "Marketing"]))
141
+ sql = query.to_sql()
142
+
143
+ assert "WHERE department IN ('Engineering','Sales','Marketing')" in sql
144
+
145
+ def test_filter_logical_in_with_sqlalchemy_column(self):
146
+ """Test filter(logical_in(...)) with SQLAlchemy column objects"""
147
+ query = self.client.query(User).filter(logical_in(User.department, ["Engineering", "Sales", "Marketing"]))
148
+ sql = query.to_sql()
149
+
150
+ assert "WHERE department IN ('Engineering','Sales','Marketing')" in sql
151
+
152
+ def test_filter_logical_in_combined_with_other_conditions(self):
153
+ """Test filter(logical_in(...)) combined with other filter conditions"""
154
+ query = (
155
+ self.client.query(Article)
156
+ .filter(Article.author == "张三")
157
+ .filter(logical_in(Article.id, boolean_match("title", "content").must("python")))
158
+ .filter("created_at > ?", "2023-01-01")
159
+ )
160
+
161
+ sql = query.to_sql()
162
+
163
+ assert "WHERE author = '张三'" in sql
164
+ assert "AND id IN (SELECT id FROM table WHERE MATCH(title, content) AGAINST('+python' IN BOOLEAN MODE))" in sql
165
+ assert "AND created_at > '2023-01-01'" in sql
166
+
167
+ def test_filter_logical_in_with_tuple_values(self):
168
+ """Test filter(logical_in(...)) with tuple values"""
169
+ query = self.client.query(User).filter(logical_in(User.id, (1, 2, 3, 4, 5)))
170
+ sql = query.to_sql()
171
+
172
+ assert "WHERE id IN (1,2,3,4,5)" in sql
173
+
174
+ def test_filter_logical_in_edge_cases(self):
175
+ """Test filter(logical_in(...)) with edge cases"""
176
+ # Test with None value
177
+ query1 = self.client.query(User).filter(logical_in(User.id, None))
178
+ sql1 = query1.to_sql()
179
+
180
+ assert "WHERE id IN (None)" in sql1
181
+
182
+ # Test with mixed types in list
183
+ query2 = self.client.query(User).filter(logical_in("id", [1, "2", 3.0]))
184
+ sql2 = query2.to_sql()
185
+
186
+ assert "WHERE id IN (1,'2',3.0)" in sql2
187
+
188
+ def test_filter_logical_in_method_chaining(self):
189
+ """Test filter(logical_in(...)) with method chaining"""
190
+ query = (
191
+ self.client.query(User)
192
+ .filter(logical_in(User.city, ["北京", "上海"]))
193
+ .filter(logical_in(User.department, ["Engineering", "Sales"]))
194
+ .filter(User.age > 25)
195
+ .order_by(User.name)
196
+ .limit(10)
197
+ )
198
+
199
+ sql = query.to_sql()
200
+
201
+ assert "WHERE city IN ('北京','上海')" in sql
202
+ assert "AND department IN ('Engineering','Sales')" in sql
203
+ assert "AND age > 25" in sql
204
+ assert "ORDER BY name" in sql
205
+ assert "LIMIT 10" in sql
206
+
207
+ def test_filter_logical_in_performance_queries(self):
208
+ """Test filter(logical_in(...)) with performance-focused queries"""
209
+ # Complex query with multiple logical_in conditions
210
+ query = (
211
+ self.client.query(User)
212
+ .select(User.name, User.department, func.count(User.id))
213
+ .filter(logical_in(User.city, ["北京", "上海", "广州", "深圳"]))
214
+ .filter(logical_in(User.department, ["Engineering", "Sales", "Marketing"]))
215
+ .filter(User.age > 25)
216
+ .group_by(User.department)
217
+ .having(func.count(User.id) > 1)
218
+ .order_by(func.count(User.id).desc())
219
+ .limit(20)
220
+ )
221
+
222
+ sql = query.to_sql()
223
+
224
+ assert "SELECT" in sql
225
+ assert "FROM users" in sql
226
+ assert "WHERE city IN ('北京','上海','广州','深圳')" in sql
227
+ assert "AND department IN ('Engineering','Sales','Marketing')" in sql
228
+ assert "AND age > 25" in sql
229
+ assert "GROUP BY department" in sql
230
+ assert "HAVING count(id) > 1" in sql
231
+ assert "ORDER BY count(id) DESC" in sql
232
+ assert "LIMIT 20" in sql
233
+
234
+
235
+ if __name__ == "__main__":
236
+ # Run tests if executed directly
237
+ pytest.main([__file__, "-v"])