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,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()