matrixone-python-sdk 0.1.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- matrixone/__init__.py +155 -0
- matrixone/account.py +723 -0
- matrixone/async_client.py +3913 -0
- matrixone/async_metadata_manager.py +311 -0
- matrixone/async_orm.py +123 -0
- matrixone/async_vector_index_manager.py +633 -0
- matrixone/base_client.py +208 -0
- matrixone/client.py +4672 -0
- matrixone/config.py +452 -0
- matrixone/connection_hooks.py +286 -0
- matrixone/exceptions.py +89 -0
- matrixone/logger.py +782 -0
- matrixone/metadata.py +820 -0
- matrixone/moctl.py +219 -0
- matrixone/orm.py +2277 -0
- matrixone/pitr.py +646 -0
- matrixone/pubsub.py +771 -0
- matrixone/restore.py +411 -0
- matrixone/search_vector_index.py +1176 -0
- matrixone/snapshot.py +550 -0
- matrixone/sql_builder.py +844 -0
- matrixone/sqlalchemy_ext/__init__.py +161 -0
- matrixone/sqlalchemy_ext/adapters.py +163 -0
- matrixone/sqlalchemy_ext/dialect.py +534 -0
- matrixone/sqlalchemy_ext/fulltext_index.py +895 -0
- matrixone/sqlalchemy_ext/fulltext_search.py +1686 -0
- matrixone/sqlalchemy_ext/hnsw_config.py +194 -0
- matrixone/sqlalchemy_ext/ivf_config.py +252 -0
- matrixone/sqlalchemy_ext/table_builder.py +351 -0
- matrixone/sqlalchemy_ext/vector_index.py +1721 -0
- matrixone/sqlalchemy_ext/vector_type.py +948 -0
- matrixone/version.py +580 -0
- matrixone_python_sdk-0.1.0.dist-info/METADATA +706 -0
- matrixone_python_sdk-0.1.0.dist-info/RECORD +122 -0
- matrixone_python_sdk-0.1.0.dist-info/WHEEL +5 -0
- matrixone_python_sdk-0.1.0.dist-info/entry_points.txt +5 -0
- matrixone_python_sdk-0.1.0.dist-info/licenses/LICENSE +200 -0
- matrixone_python_sdk-0.1.0.dist-info/top_level.txt +2 -0
- tests/__init__.py +19 -0
- tests/offline/__init__.py +20 -0
- tests/offline/conftest.py +77 -0
- tests/offline/test_account.py +703 -0
- tests/offline/test_async_client_query_comprehensive.py +1218 -0
- tests/offline/test_basic.py +54 -0
- tests/offline/test_case_sensitivity.py +227 -0
- tests/offline/test_connection_hooks_offline.py +287 -0
- tests/offline/test_dialect_schema_handling.py +609 -0
- tests/offline/test_explain_methods.py +346 -0
- tests/offline/test_filter_logical_in.py +237 -0
- tests/offline/test_fulltext_search_comprehensive.py +795 -0
- tests/offline/test_ivf_config.py +249 -0
- tests/offline/test_join_methods.py +281 -0
- tests/offline/test_join_sqlalchemy_compatibility.py +276 -0
- tests/offline/test_logical_in_method.py +237 -0
- tests/offline/test_matrixone_version_parsing.py +264 -0
- tests/offline/test_metadata_offline.py +557 -0
- tests/offline/test_moctl.py +300 -0
- tests/offline/test_moctl_simple.py +251 -0
- tests/offline/test_model_support_offline.py +359 -0
- tests/offline/test_model_support_simple.py +225 -0
- tests/offline/test_pinecone_filter_offline.py +377 -0
- tests/offline/test_pitr.py +585 -0
- tests/offline/test_pubsub.py +712 -0
- tests/offline/test_query_update.py +283 -0
- tests/offline/test_restore.py +445 -0
- tests/offline/test_snapshot_comprehensive.py +384 -0
- tests/offline/test_sql_escaping_edge_cases.py +551 -0
- tests/offline/test_sqlalchemy_integration.py +382 -0
- tests/offline/test_sqlalchemy_vector_integration.py +434 -0
- tests/offline/test_table_builder.py +198 -0
- tests/offline/test_unified_filter.py +398 -0
- tests/offline/test_unified_transaction.py +495 -0
- tests/offline/test_vector_index.py +238 -0
- tests/offline/test_vector_operations.py +688 -0
- tests/offline/test_vector_type.py +174 -0
- tests/offline/test_version_core.py +328 -0
- tests/offline/test_version_management.py +372 -0
- tests/offline/test_version_standalone.py +652 -0
- tests/online/__init__.py +20 -0
- tests/online/conftest.py +216 -0
- tests/online/test_account_management.py +194 -0
- tests/online/test_advanced_features.py +344 -0
- tests/online/test_async_client_interfaces.py +330 -0
- tests/online/test_async_client_online.py +285 -0
- tests/online/test_async_model_insert_online.py +293 -0
- tests/online/test_async_orm_online.py +300 -0
- tests/online/test_async_simple_query_online.py +802 -0
- tests/online/test_async_transaction_simple_query.py +300 -0
- tests/online/test_basic_connection.py +130 -0
- tests/online/test_client_online.py +238 -0
- tests/online/test_config.py +90 -0
- tests/online/test_config_validation.py +123 -0
- tests/online/test_connection_hooks_new_online.py +217 -0
- tests/online/test_dialect_schema_handling_online.py +331 -0
- tests/online/test_filter_logical_in_online.py +374 -0
- tests/online/test_fulltext_comprehensive.py +1773 -0
- tests/online/test_fulltext_label_online.py +433 -0
- tests/online/test_fulltext_search_online.py +842 -0
- tests/online/test_ivf_stats_online.py +506 -0
- tests/online/test_logger_integration.py +311 -0
- tests/online/test_matrixone_query_orm.py +540 -0
- tests/online/test_metadata_online.py +579 -0
- tests/online/test_model_insert_online.py +255 -0
- tests/online/test_mysql_driver_validation.py +213 -0
- tests/online/test_orm_advanced_features.py +2022 -0
- tests/online/test_orm_cte_integration.py +269 -0
- tests/online/test_orm_online.py +270 -0
- tests/online/test_pinecone_filter.py +708 -0
- tests/online/test_pubsub_operations.py +352 -0
- tests/online/test_query_methods.py +225 -0
- tests/online/test_query_update_online.py +433 -0
- tests/online/test_search_vector_index.py +557 -0
- tests/online/test_simple_fulltext_online.py +915 -0
- tests/online/test_snapshot_comprehensive.py +998 -0
- tests/online/test_sqlalchemy_engine_integration.py +336 -0
- tests/online/test_sqlalchemy_integration.py +425 -0
- tests/online/test_transaction_contexts.py +1219 -0
- tests/online/test_transaction_insert_methods.py +356 -0
- tests/online/test_transaction_query_methods.py +288 -0
- tests/online/test_unified_filter_online.py +529 -0
- tests/online/test_vector_comprehensive.py +706 -0
- tests/online/test_version_management.py +291 -0
@@ -0,0 +1,433 @@
|
|
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
|
+
Online tests for FulltextFilter label functionality with real MatrixOne database
|
19
|
+
Tests AS score feature with actual data
|
20
|
+
"""
|
21
|
+
|
22
|
+
import unittest
|
23
|
+
import sys
|
24
|
+
import os
|
25
|
+
|
26
|
+
# Add the parent directory to sys.path to import matrixone
|
27
|
+
sys.path.insert(0, os.path.join(os.path.dirname(__file__), '..', '..'))
|
28
|
+
|
29
|
+
from matrixone import Client
|
30
|
+
from matrixone.config import get_connection_kwargs
|
31
|
+
from matrixone.sqlalchemy_ext.fulltext_search import boolean_match, natural_match, group
|
32
|
+
|
33
|
+
try:
|
34
|
+
from sqlalchemy.orm import declarative_base
|
35
|
+
except ImportError:
|
36
|
+
from matrixone.orm import declarative_base
|
37
|
+
|
38
|
+
from sqlalchemy import Column, Integer, String, Text
|
39
|
+
|
40
|
+
|
41
|
+
class TestFulltextLabelOnline(unittest.TestCase):
|
42
|
+
"""Online tests for FulltextFilter label functionality."""
|
43
|
+
|
44
|
+
@classmethod
|
45
|
+
def setUpClass(cls):
|
46
|
+
"""Set up test database and data."""
|
47
|
+
# Get connection parameters
|
48
|
+
conn_params = get_connection_kwargs()
|
49
|
+
# Filter out unsupported parameters
|
50
|
+
client_params = {k: v for k, v in conn_params.items() if k in ['host', 'port', 'user', 'password', 'database']}
|
51
|
+
|
52
|
+
cls.client = Client()
|
53
|
+
cls.client.connect(**client_params)
|
54
|
+
|
55
|
+
# Create test database
|
56
|
+
cls.test_db = "test_fulltext_label"
|
57
|
+
try:
|
58
|
+
cls.client.execute(f"DROP DATABASE IF EXISTS {cls.test_db}")
|
59
|
+
cls.client.execute(f"CREATE DATABASE {cls.test_db}")
|
60
|
+
cls.client.execute(f"USE {cls.test_db}")
|
61
|
+
except Exception as e:
|
62
|
+
print(f"Database setup warning: {e}")
|
63
|
+
|
64
|
+
# Define model
|
65
|
+
cls.Base = declarative_base()
|
66
|
+
|
67
|
+
class Article(cls.Base):
|
68
|
+
__tablename__ = "test_articles"
|
69
|
+
id = Column(Integer, primary_key=True)
|
70
|
+
title = Column(String(200))
|
71
|
+
content = Column(Text)
|
72
|
+
tags = Column(String(500))
|
73
|
+
category = Column(String(50))
|
74
|
+
|
75
|
+
cls.Article = Article
|
76
|
+
|
77
|
+
# Create table
|
78
|
+
cls.client.execute(
|
79
|
+
"""
|
80
|
+
CREATE TABLE IF NOT EXISTS test_articles (
|
81
|
+
id INT AUTO_INCREMENT PRIMARY KEY,
|
82
|
+
title VARCHAR(200),
|
83
|
+
content TEXT,
|
84
|
+
tags VARCHAR(500),
|
85
|
+
category VARCHAR(50)
|
86
|
+
)
|
87
|
+
"""
|
88
|
+
)
|
89
|
+
|
90
|
+
# Insert test data
|
91
|
+
test_articles = [
|
92
|
+
{
|
93
|
+
'title': 'Python Programming Tutorial',
|
94
|
+
'content': 'Learn Python programming with this comprehensive tutorial. Python is a powerful language for machine learning and web development.',
|
95
|
+
'tags': 'python, programming, tutorial, beginner',
|
96
|
+
'category': 'Programming',
|
97
|
+
},
|
98
|
+
{
|
99
|
+
'title': 'Machine Learning Basics',
|
100
|
+
'content': 'Introduction to machine learning concepts. This tutorial covers supervised and unsupervised learning algorithms.',
|
101
|
+
'tags': 'machine learning, AI, algorithms, tutorial',
|
102
|
+
'category': 'AI',
|
103
|
+
},
|
104
|
+
{
|
105
|
+
'title': 'Java vs Python Comparison',
|
106
|
+
'content': 'Comparing Java and Python programming languages. Both are popular for enterprise development and data science.',
|
107
|
+
'tags': 'java, python, comparison, programming',
|
108
|
+
'category': 'Programming',
|
109
|
+
},
|
110
|
+
{
|
111
|
+
'title': 'Deprecated Python Features',
|
112
|
+
'content': 'Old Python features that should be avoided. Legacy code patterns and deprecated functions.',
|
113
|
+
'tags': 'python, deprecated, legacy, old',
|
114
|
+
'category': 'Programming',
|
115
|
+
},
|
116
|
+
{
|
117
|
+
'title': 'Neural Network Guide',
|
118
|
+
'content': 'Deep learning with neural networks. Advanced machine learning tutorial for experienced developers.',
|
119
|
+
'tags': 'neural networks, deep learning, advanced, machine learning',
|
120
|
+
'category': 'AI',
|
121
|
+
},
|
122
|
+
{
|
123
|
+
'title': 'Web Development with Python',
|
124
|
+
'content': 'Building web applications using Python frameworks like Django and Flask. Modern web development practices.',
|
125
|
+
'tags': 'python, web development, django, flask',
|
126
|
+
'category': 'Web',
|
127
|
+
},
|
128
|
+
]
|
129
|
+
|
130
|
+
for article_data in test_articles:
|
131
|
+
sql = f"""INSERT INTO test_articles (title, content, tags, category) VALUES
|
132
|
+
('{article_data['title'].replace("'", "''")}',
|
133
|
+
'{article_data['content'].replace("'", "''")}',
|
134
|
+
'{article_data['tags'].replace("'", "''")}',
|
135
|
+
'{article_data['category'].replace("'", "''")}'
|
136
|
+
)"""
|
137
|
+
cls.client.execute(sql)
|
138
|
+
|
139
|
+
# Create fulltext indexes
|
140
|
+
try:
|
141
|
+
cls.client.execute("CREATE FULLTEXT INDEX ft_title_content ON test_articles(title, content)")
|
142
|
+
except Exception as e:
|
143
|
+
print(f"Fulltext index creation warning: {e}")
|
144
|
+
|
145
|
+
try:
|
146
|
+
cls.client.execute("CREATE FULLTEXT INDEX ft_tags ON test_articles(tags)")
|
147
|
+
except Exception as e:
|
148
|
+
print(f"Tags index creation warning: {e}")
|
149
|
+
|
150
|
+
@classmethod
|
151
|
+
def tearDownClass(cls):
|
152
|
+
"""Clean up test database."""
|
153
|
+
try:
|
154
|
+
cls.client.execute(f"DROP DATABASE IF EXISTS {cls.test_db}")
|
155
|
+
except Exception as e:
|
156
|
+
print(f"Cleanup warning: {e}")
|
157
|
+
|
158
|
+
cls.client.disconnect()
|
159
|
+
|
160
|
+
def test_basic_boolean_label_query(self):
|
161
|
+
"""Test basic boolean query with label using Client.query()."""
|
162
|
+
# Query with label
|
163
|
+
results = self.client.query(
|
164
|
+
self.Article.id,
|
165
|
+
self.Article.title,
|
166
|
+
boolean_match("title", "content").must("python").label("python_score"),
|
167
|
+
).all()
|
168
|
+
|
169
|
+
# Should find articles mentioning Python
|
170
|
+
self.assertGreater(len(results), 0)
|
171
|
+
|
172
|
+
# Check result structure
|
173
|
+
for row in results:
|
174
|
+
self.assertEqual(len(row), 3) # id, title, score
|
175
|
+
article_id, title, score = row
|
176
|
+
self.assertIsInstance(article_id, int)
|
177
|
+
self.assertIsInstance(title, str)
|
178
|
+
self.assertIn("python", title.lower())
|
179
|
+
# Score should be a float or None
|
180
|
+
self.assertTrue(score is None or isinstance(score, (int, float)))
|
181
|
+
|
182
|
+
def test_natural_language_label_query(self):
|
183
|
+
"""Test natural language query with label."""
|
184
|
+
results = self.client.query(
|
185
|
+
self.Article.id,
|
186
|
+
self.Article.title,
|
187
|
+
natural_match("title", "content", query="machine learning").label("ml_score"),
|
188
|
+
).all()
|
189
|
+
|
190
|
+
# Should find machine learning articles
|
191
|
+
self.assertGreater(len(results), 0)
|
192
|
+
|
193
|
+
for row in results:
|
194
|
+
article_id, title, score = row
|
195
|
+
self.assertIsInstance(article_id, int)
|
196
|
+
self.assertIsInstance(title, str)
|
197
|
+
|
198
|
+
def test_complex_boolean_with_label(self):
|
199
|
+
"""Test complex boolean query with multiple operators and label."""
|
200
|
+
results = self.client.query(
|
201
|
+
self.Article.id,
|
202
|
+
self.Article.title,
|
203
|
+
boolean_match("title", "content")
|
204
|
+
.must("programming")
|
205
|
+
.encourage("tutorial")
|
206
|
+
.discourage("deprecated")
|
207
|
+
.label("complex_score"),
|
208
|
+
).all()
|
209
|
+
|
210
|
+
# Should find programming articles, prefer tutorials, avoid deprecated
|
211
|
+
self.assertGreater(len(results), 0)
|
212
|
+
|
213
|
+
# Verify structure
|
214
|
+
for row in results:
|
215
|
+
article_id, title, score = row
|
216
|
+
self.assertIsInstance(article_id, int)
|
217
|
+
self.assertIsInstance(title, str)
|
218
|
+
|
219
|
+
def test_multiple_fulltext_labels(self):
|
220
|
+
"""Test multiple fulltext expressions with different labels."""
|
221
|
+
results = self.client.query(
|
222
|
+
self.Article.id,
|
223
|
+
self.Article.title,
|
224
|
+
boolean_match("title", "content").must("python").label("python_score"),
|
225
|
+
boolean_match("tags").must("tutorial").label("tutorial_score"),
|
226
|
+
).all()
|
227
|
+
|
228
|
+
# Should return id, title, python_score, tutorial_score
|
229
|
+
for row in results:
|
230
|
+
self.assertEqual(len(row), 4)
|
231
|
+
article_id, title, python_score, tutorial_score = row
|
232
|
+
self.assertIsInstance(article_id, int)
|
233
|
+
self.assertIsInstance(title, str)
|
234
|
+
|
235
|
+
def test_label_with_filters(self):
|
236
|
+
"""Test label queries combined with regular filters."""
|
237
|
+
results = (
|
238
|
+
self.client.query(
|
239
|
+
self.Article.id,
|
240
|
+
self.Article.title,
|
241
|
+
self.Article.category,
|
242
|
+
boolean_match("title", "content").must("python").label("score"),
|
243
|
+
)
|
244
|
+
.filter(self.Article.category == "Programming")
|
245
|
+
.all()
|
246
|
+
)
|
247
|
+
|
248
|
+
# Should find Python articles in Programming category
|
249
|
+
for row in results:
|
250
|
+
article_id, title, category, score = row
|
251
|
+
self.assertEqual(category, "Programming")
|
252
|
+
self.assertIn("python", title.lower())
|
253
|
+
|
254
|
+
def test_label_with_ordering(self):
|
255
|
+
"""Test ordering by fulltext score."""
|
256
|
+
# Note: This tests the SQL generation, actual score ordering depends on MatrixOne implementation
|
257
|
+
results = self.client.query(
|
258
|
+
self.Article.id,
|
259
|
+
self.Article.title,
|
260
|
+
boolean_match("title", "content").must("python").label("relevance"),
|
261
|
+
).all()
|
262
|
+
|
263
|
+
# Should execute without errors
|
264
|
+
self.assertGreater(len(results), 0)
|
265
|
+
|
266
|
+
def test_group_operations_with_label(self):
|
267
|
+
"""Test group operations with label."""
|
268
|
+
results = self.client.query(
|
269
|
+
self.Article.id,
|
270
|
+
self.Article.title,
|
271
|
+
boolean_match("title", "content").must("programming").must(group().medium("python", "java")).label("lang_score"),
|
272
|
+
).all()
|
273
|
+
|
274
|
+
# Should find articles with programming AND (python OR java)
|
275
|
+
for row in results:
|
276
|
+
article_id, title, score = row
|
277
|
+
title_lower = title.lower()
|
278
|
+
self.assertTrue("python" in title_lower or "java" in title_lower)
|
279
|
+
|
280
|
+
def test_phrase_search_with_label(self):
|
281
|
+
"""Test phrase search with label."""
|
282
|
+
# Note: MatrixOne has limitations with phrase operators in complex queries
|
283
|
+
# Using simple term matching instead to test the label functionality
|
284
|
+
results = self.client.query(
|
285
|
+
self.Article.id,
|
286
|
+
self.Article.title,
|
287
|
+
boolean_match("title", "content").must("machine").encourage("learning").label("phrase_score"),
|
288
|
+
).all()
|
289
|
+
|
290
|
+
# Should execute without errors and find relevant articles
|
291
|
+
self.assertIsInstance(results, list)
|
292
|
+
# Should find machine learning related articles
|
293
|
+
if results:
|
294
|
+
for row in results:
|
295
|
+
article_id, title, score = row
|
296
|
+
self.assertIsInstance(article_id, int)
|
297
|
+
self.assertIsInstance(title, str)
|
298
|
+
|
299
|
+
def test_prefix_search_with_label(self):
|
300
|
+
"""Test prefix search with label."""
|
301
|
+
results = self.client.query(
|
302
|
+
self.Article.id,
|
303
|
+
self.Article.title,
|
304
|
+
boolean_match("title", "content")
|
305
|
+
.must(group().prefix("program")) # Should match "programming"
|
306
|
+
.label("prefix_score"),
|
307
|
+
).all()
|
308
|
+
|
309
|
+
# Should find articles with words starting with "program"
|
310
|
+
self.assertGreater(len(results), 0)
|
311
|
+
|
312
|
+
def test_weight_operators_with_label(self):
|
313
|
+
"""Test weight operators within groups with label."""
|
314
|
+
results = self.client.query(
|
315
|
+
self.Article.id,
|
316
|
+
self.Article.title,
|
317
|
+
boolean_match("title", "content")
|
318
|
+
.must("python")
|
319
|
+
.must(group().high("tutorial").low("deprecated"))
|
320
|
+
.label("weighted_score"),
|
321
|
+
).all()
|
322
|
+
|
323
|
+
# Should execute and find relevant articles
|
324
|
+
self.assertGreater(len(results), 0)
|
325
|
+
|
326
|
+
def test_single_column_label(self):
|
327
|
+
"""Test single column fulltext search with label."""
|
328
|
+
# Note: MatrixOne requires MATCH columns to be covered by existing fulltext index
|
329
|
+
# Since we have ft_title_content(title, content), we need to search both columns
|
330
|
+
results = self.client.query(
|
331
|
+
self.Article.id,
|
332
|
+
self.Article.title,
|
333
|
+
boolean_match("title", "content").must("Python").label("title_score"),
|
334
|
+
).all()
|
335
|
+
|
336
|
+
# Should find articles with "Python" in title or content
|
337
|
+
for row in results:
|
338
|
+
article_id, title, score = row
|
339
|
+
# Python could be in title or content
|
340
|
+
self.assertTrue("Python" in title or "python" in title.lower())
|
341
|
+
|
342
|
+
def test_tags_column_label(self):
|
343
|
+
"""Test fulltext search on tags column with label."""
|
344
|
+
results = self.client.query(
|
345
|
+
self.Article.id,
|
346
|
+
self.Article.tags,
|
347
|
+
boolean_match("tags").must("tutorial").label("tag_score"),
|
348
|
+
).all()
|
349
|
+
|
350
|
+
# Should find articles tagged with "tutorial"
|
351
|
+
for row in results:
|
352
|
+
article_id, tags, score = row
|
353
|
+
self.assertIn("tutorial", tags.lower())
|
354
|
+
|
355
|
+
def test_empty_result_with_label(self):
|
356
|
+
"""Test query that returns no results with label."""
|
357
|
+
results = self.client.query(
|
358
|
+
self.Article.id,
|
359
|
+
self.Article.title,
|
360
|
+
boolean_match("title", "content").must("nonexistent").label("empty_score"),
|
361
|
+
).all()
|
362
|
+
|
363
|
+
# Should return empty list without errors
|
364
|
+
self.assertEqual(len(results), 0)
|
365
|
+
|
366
|
+
def test_label_sql_generation_verification(self):
|
367
|
+
"""Test that generated SQL contains correct AS clause."""
|
368
|
+
query = self.client.query(
|
369
|
+
self.Article.id,
|
370
|
+
boolean_match("title", "content").must("test").label("verification_score"),
|
371
|
+
)
|
372
|
+
|
373
|
+
# Get the generated SQL
|
374
|
+
sql, params = query._build_sql()
|
375
|
+
|
376
|
+
# Verify SQL contains AS clause
|
377
|
+
self.assertIn("AS verification_score", sql)
|
378
|
+
self.assertIn("MATCH(title, content)", sql)
|
379
|
+
self.assertIn("AGAINST(", sql)
|
380
|
+
|
381
|
+
def test_matrixone_compatibility_online(self):
|
382
|
+
"""Test compatibility with actual MatrixOne syntax."""
|
383
|
+
# Test natural language mode (like MatrixOne test cases)
|
384
|
+
results = self.client.query(
|
385
|
+
self.Article.id,
|
386
|
+
natural_match("title", "content", query="python tutorial").label("score"),
|
387
|
+
).all()
|
388
|
+
|
389
|
+
# Should execute without syntax errors
|
390
|
+
self.assertIsInstance(results, list)
|
391
|
+
|
392
|
+
# Test boolean mode
|
393
|
+
results = self.client.query(
|
394
|
+
self.Article.id,
|
395
|
+
boolean_match("title", "content").must("python").encourage("tutorial").label("score"),
|
396
|
+
).all()
|
397
|
+
|
398
|
+
# Should execute without syntax errors
|
399
|
+
self.assertIsInstance(results, list)
|
400
|
+
|
401
|
+
def test_concurrent_label_queries(self):
|
402
|
+
"""Test multiple label queries to ensure no conflicts."""
|
403
|
+
# Run multiple queries with different labels
|
404
|
+
query1 = self.client.query(self.Article.id, boolean_match("title", "content").must("python").label("score1"))
|
405
|
+
|
406
|
+
query2 = self.client.query(self.Article.id, boolean_match("title", "content").must("machine").label("score2"))
|
407
|
+
|
408
|
+
results1 = query1.all()
|
409
|
+
results2 = query2.all()
|
410
|
+
|
411
|
+
# Both should work independently
|
412
|
+
self.assertIsInstance(results1, list)
|
413
|
+
self.assertIsInstance(results2, list)
|
414
|
+
|
415
|
+
def test_label_with_limit_offset(self):
|
416
|
+
"""Test label queries with limit and offset."""
|
417
|
+
results = (
|
418
|
+
self.client.query(
|
419
|
+
self.Article.id,
|
420
|
+
self.Article.title,
|
421
|
+
boolean_match("title", "content").must("programming").label("score"),
|
422
|
+
)
|
423
|
+
.limit(2)
|
424
|
+
.offset(0)
|
425
|
+
.all()
|
426
|
+
)
|
427
|
+
|
428
|
+
# Should respect limit
|
429
|
+
self.assertLessEqual(len(results), 2)
|
430
|
+
|
431
|
+
|
432
|
+
if __name__ == '__main__':
|
433
|
+
unittest.main()
|