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,276 @@
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 join methods compatibility with SQLAlchemy ORM
19
+
20
+ This test file verifies that MatrixOne's join methods produce the same SQL
21
+ as SQLAlchemy ORM for equivalent operations.
22
+ """
23
+
24
+ import sys
25
+ import os
26
+ import unittest
27
+ from unittest.mock import Mock
28
+
29
+ # Add the matrixone package to the path
30
+ sys.path.insert(0, os.path.join(os.path.dirname(__file__), '..', '..'))
31
+
32
+ from matrixone.orm import MatrixOneQuery, declarative_base
33
+ from sqlalchemy import Column, Integer, String, create_engine, select
34
+ from sqlalchemy.orm import sessionmaker
35
+
36
+ Base = declarative_base()
37
+
38
+
39
+ class User(Base):
40
+ __tablename__ = 'users'
41
+ id = Column(Integer, primary_key=True)
42
+ name = Column(String(50))
43
+ department_id = Column(Integer)
44
+
45
+
46
+ class Address(Base):
47
+ __tablename__ = 'addresses'
48
+ id = Column(Integer, primary_key=True)
49
+ user_id = Column(Integer)
50
+ email = Column(String(100))
51
+
52
+
53
+ class Department(Base):
54
+ __tablename__ = 'departments'
55
+ id = Column(Integer, primary_key=True)
56
+ name = Column(String(50))
57
+
58
+
59
+ class TestJoinSQLAlchemyCompatibility(unittest.TestCase):
60
+ """Test join methods compatibility with SQLAlchemy ORM"""
61
+
62
+ def setUp(self):
63
+ """Set up test fixtures"""
64
+ self.mock_client = Mock()
65
+ self.query = MatrixOneQuery(User, self.mock_client)
66
+ self.query._table_name = 'users'
67
+
68
+ def test_join_behavior_matches_sqlalchemy(self):
69
+ """Test that join behavior matches SQLAlchemy ORM"""
70
+ # MatrixOne query
71
+ matrixone_query = MatrixOneQuery(User, self.mock_client)
72
+ matrixone_query._table_name = 'users'
73
+ matrixone_query.join(Address, User.id == Address.user_id)
74
+
75
+ # SQLAlchemy equivalent
76
+ sqlalchemy_query = select(User).join(Address, User.id == Address.user_id)
77
+
78
+ # Both should produce INNER JOIN
79
+ self.assertTrue(matrixone_query._joins[0].startswith("INNER JOIN"))
80
+
81
+ # MatrixOne should handle the SQLAlchemy expression correctly
82
+ self.assertIn("addresses", matrixone_query._joins[0])
83
+ self.assertIn("id = user_id", matrixone_query._joins[0])
84
+
85
+ def test_left_join_behavior_matches_sqlalchemy(self):
86
+ """Test that left join behavior matches SQLAlchemy ORM"""
87
+ # MatrixOne query
88
+ matrixone_query = MatrixOneQuery(User, self.mock_client)
89
+ matrixone_query._table_name = 'users'
90
+ matrixone_query.join(Address, User.id == Address.user_id, isouter=True)
91
+
92
+ # SQLAlchemy equivalent
93
+ sqlalchemy_query = select(User).outerjoin(Address, User.id == Address.user_id)
94
+
95
+ # Both should produce LEFT OUTER JOIN
96
+ self.assertTrue(matrixone_query._joins[0].startswith("LEFT OUTER JOIN"))
97
+ self.assertIn("addresses", matrixone_query._joins[0])
98
+
99
+ def test_join_without_condition_matches_sqlalchemy(self):
100
+ """Test that join without condition matches SQLAlchemy ORM"""
101
+ # MatrixOne query
102
+ matrixone_query = MatrixOneQuery(User, self.mock_client)
103
+ matrixone_query._table_name = 'users'
104
+ matrixone_query.join(Address)
105
+
106
+ # SQLAlchemy equivalent
107
+ sqlalchemy_query = select(User).join(Address)
108
+
109
+ # Both should produce INNER JOIN without ON condition
110
+ self.assertTrue(matrixone_query._joins[0].startswith("INNER JOIN"))
111
+ self.assertIn("addresses", matrixone_query._joins[0])
112
+ self.assertNotIn("ON", matrixone_query._joins[0])
113
+
114
+ def test_multiple_joins_matches_sqlalchemy(self):
115
+ """Test that multiple joins match SQLAlchemy ORM"""
116
+ # MatrixOne query
117
+ matrixone_query = MatrixOneQuery(User, self.mock_client)
118
+ matrixone_query._table_name = 'users'
119
+ matrixone_query.join(Address, User.id == Address.user_id)
120
+ matrixone_query.join(Department, User.department_id == Department.id)
121
+
122
+ # SQLAlchemy equivalent
123
+ sqlalchemy_query = (
124
+ select(User).join(Address, User.id == Address.user_id).join(Department, User.department_id == Department.id)
125
+ )
126
+
127
+ # Both should have two joins
128
+ self.assertEqual(len(matrixone_query._joins), 2)
129
+ self.assertTrue(matrixone_query._joins[0].startswith("INNER JOIN"))
130
+ self.assertTrue(matrixone_query._joins[1].startswith("INNER JOIN"))
131
+ self.assertIn("addresses", matrixone_query._joins[0])
132
+ self.assertIn("departments", matrixone_query._joins[1])
133
+
134
+ def test_join_with_string_condition(self):
135
+ """Test join with string condition (MatrixOne specific)"""
136
+ # MatrixOne query with string condition
137
+ matrixone_query = MatrixOneQuery(User, self.mock_client)
138
+ matrixone_query._table_name = 'users'
139
+ matrixone_query.join('addresses', 'users.id = addresses.user_id')
140
+
141
+ # Should produce INNER JOIN with the exact condition
142
+ self.assertTrue(matrixone_query._joins[0].startswith("INNER JOIN"))
143
+ self.assertIn("addresses", matrixone_query._joins[0])
144
+ self.assertIn("users.id = addresses.user_id", matrixone_query._joins[0])
145
+
146
+ def test_join_with_onclause_parameter(self):
147
+ """Test join with onclause parameter (SQLAlchemy style)"""
148
+ # MatrixOne query with onclause parameter
149
+ matrixone_query = MatrixOneQuery(User, self.mock_client)
150
+ matrixone_query._table_name = 'users'
151
+ matrixone_query.join('addresses', onclause='users.id = addresses.user_id')
152
+
153
+ # Should produce INNER JOIN with the exact condition
154
+ self.assertTrue(matrixone_query._joins[0].startswith("INNER JOIN"))
155
+ self.assertIn("addresses", matrixone_query._joins[0])
156
+ self.assertIn("users.id = addresses.user_id", matrixone_query._joins[0])
157
+
158
+ def test_join_method_aliases(self):
159
+ """Test that join method aliases work correctly"""
160
+ # Test innerjoin
161
+ query1 = MatrixOneQuery(User, self.mock_client)
162
+ query1._table_name = 'users'
163
+ query1.innerjoin(Address, User.id == Address.user_id)
164
+
165
+ # Test leftjoin
166
+ query2 = MatrixOneQuery(User, self.mock_client)
167
+ query2._table_name = 'users'
168
+ query2.leftjoin(Address, User.id == Address.user_id)
169
+
170
+ # Test outerjoin
171
+ query3 = MatrixOneQuery(User, self.mock_client)
172
+ query3._table_name = 'users'
173
+ query3.outerjoin(Address, User.id == Address.user_id)
174
+
175
+ # Test fullouterjoin
176
+ query4 = MatrixOneQuery(User, self.mock_client)
177
+ query4._table_name = 'users'
178
+ query4.fullouterjoin(Address, User.id == Address.user_id)
179
+
180
+ # Verify join types
181
+ self.assertTrue(query1._joins[0].startswith("INNER JOIN"))
182
+ self.assertTrue(query2._joins[0].startswith("LEFT OUTER JOIN"))
183
+ self.assertTrue(query3._joins[0].startswith("LEFT OUTER JOIN"))
184
+ self.assertTrue(query4._joins[0].startswith("FULL OUTER JOIN"))
185
+
186
+ def test_join_with_complex_expression(self):
187
+ """Test join with complex SQLAlchemy expression"""
188
+ # MatrixOne query with complex expression
189
+ matrixone_query = MatrixOneQuery(User, self.mock_client)
190
+ matrixone_query._table_name = 'users'
191
+ matrixone_query.join(Address, (User.id == Address.user_id) & (Address.email.like('%@example.com')))
192
+
193
+ # Should handle complex expressions
194
+ self.assertTrue(matrixone_query._joins[0].startswith("INNER JOIN"))
195
+ self.assertIn("addresses", matrixone_query._joins[0])
196
+ self.assertIn("ON", matrixone_query._joins[0])
197
+ # The complex expression should be compiled to SQL
198
+ join_clause = matrixone_query._joins[0]
199
+ self.assertIn("id = user_id", join_clause)
200
+
201
+ def test_join_method_chaining_compatibility(self):
202
+ """Test that join methods support method chaining like SQLAlchemy"""
203
+ # MatrixOne query with method chaining
204
+ result = (
205
+ MatrixOneQuery(User, self.mock_client)
206
+ .join(Address, User.id == Address.user_id)
207
+ .join(Department, User.department_id == Department.id)
208
+ .filter(User.name == 'John')
209
+ )
210
+
211
+ # Should return self for chaining
212
+ self.assertEqual(result._table_name, 'users')
213
+ self.assertEqual(len(result._joins), 2)
214
+ self.assertEqual(len(result._where_conditions), 1)
215
+
216
+ def test_join_with_model_class_target(self):
217
+ """Test join with model class as target"""
218
+ # MatrixOne query with model class
219
+ matrixone_query = MatrixOneQuery(User, self.mock_client)
220
+ matrixone_query._table_name = 'users'
221
+ matrixone_query.join(Address, 'users.id = addresses.user_id')
222
+
223
+ # Should use the model's table name
224
+ self.assertTrue(matrixone_query._joins[0].startswith("INNER JOIN"))
225
+ self.assertIn("addresses", matrixone_query._joins[0])
226
+ self.assertIn("users.id = addresses.user_id", matrixone_query._joins[0])
227
+
228
+ def test_join_parameter_consistency_with_sqlalchemy(self):
229
+ """Test that parameter names are consistent with SQLAlchemy"""
230
+ # Test that onclause parameter works (SQLAlchemy style)
231
+ query1 = MatrixOneQuery(User, self.mock_client)
232
+ query1._table_name = 'users'
233
+ query1.join('addresses', onclause='users.id = addresses.user_id')
234
+
235
+ # Test that positional parameter also works (backward compatibility)
236
+ query2 = MatrixOneQuery(User, self.mock_client)
237
+ query2._table_name = 'users'
238
+ query2.join('addresses', 'users.id = addresses.user_id')
239
+
240
+ # Both should produce the same result
241
+ self.assertEqual(query1._joins[0], query2._joins[0])
242
+
243
+ def test_join_default_behavior_is_inner_join(self):
244
+ """Test that join() defaults to INNER JOIN like SQLAlchemy"""
245
+ # MatrixOne query
246
+ matrixone_query = MatrixOneQuery(User, self.mock_client)
247
+ matrixone_query._table_name = 'users'
248
+ matrixone_query.join(Address, User.id == Address.user_id)
249
+
250
+ # SQLAlchemy equivalent
251
+ sqlalchemy_query = select(User).join(Address, User.id == Address.user_id)
252
+
253
+ # Both should default to INNER JOIN
254
+ self.assertTrue(matrixone_query._joins[0].startswith("INNER JOIN"))
255
+
256
+ # This matches SQLAlchemy's default behavior where join() creates INNER JOIN
257
+
258
+
259
+ if __name__ == '__main__':
260
+ # Create test suite
261
+ test_suite = unittest.TestSuite()
262
+
263
+ # Add all test methods
264
+ test_suite.addTest(unittest.makeSuite(TestJoinSQLAlchemyCompatibility))
265
+
266
+ # Run the tests
267
+ runner = unittest.TextTestRunner(verbosity=2)
268
+ result = runner.run(test_suite)
269
+
270
+ # Print summary
271
+ print(f"\n{'='*50}")
272
+ print(f"Tests run: {result.testsRun}")
273
+ print(f"Failures: {len(result.failures)}")
274
+ print(f"Errors: {len(result.errors)}")
275
+ print(f"Success rate: {((result.testsRun - len(result.failures) - len(result.errors)) / result.testsRun * 100):.1f}%")
276
+ print(f"{'='*50}")
@@ -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: logical_in method for enhanced IN functionality
19
+
20
+ This test verifies that the logical_in method 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 TestLogicalInMethod:
62
+ """Test class for logical_in method functionality"""
63
+
64
+ def setup_method(self):
65
+ """Set up test client"""
66
+ self.client = Client("test://localhost:6001/test")
67
+
68
+ def test_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_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_logical_in_with_fulltext_filter(self):
98
+ """Test 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_logical_in_with_subquery(self):
121
+ """Test 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_logical_in_with_sqlalchemy_expressions(self):
131
+ """Test 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_logical_in_with_string_column(self):
139
+ """Test 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_logical_in_with_sqlalchemy_column(self):
146
+ """Test 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_logical_in_combined_with_other_conditions(self):
153
+ """Test 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_logical_in_with_tuple_values(self):
168
+ """Test 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_logical_in_edge_cases(self):
175
+ """Test 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_logical_in_method_chaining(self):
189
+ """Test 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_logical_in_performance_queries(self):
208
+ """Test 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"])