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,377 @@
1
+ # Copyright 2021 - 2022 Matrix Origin
2
+ #
3
+ # Licensed under the Apache License, Version 2.0 (the "License");
4
+ # you may not use this file except in compliance with the License.
5
+ # You may obtain a copy of the License at
6
+ #
7
+ # http://www.apache.org/licenses/LICENSE-2.0
8
+ #
9
+ # Unless required by applicable law or agreed to in writing, software
10
+ # distributed under the License is distributed on an "AS IS" BASIS,
11
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ # See the License for the specific language governing permissions and
13
+ # limitations under the License.
14
+
15
+ """
16
+ Offline tests for Pinecone-compatible filter functionality.
17
+ These tests focus on logic validation, parameter handling, and result structure
18
+ without requiring actual database connections.
19
+ """
20
+
21
+ import pytest
22
+ from unittest.mock import Mock, MagicMock, patch
23
+ from typing import List, Dict, Any
24
+ from matrixone.search_vector_index import PineconeCompatibleIndex, QueryResponse, VectorMatch
25
+
26
+
27
+ class TestPineconeFilterOffline:
28
+ """Offline tests for Pinecone-compatible filter functionality"""
29
+
30
+ @pytest.fixture
31
+ def mock_client(self):
32
+ """Create mock client for testing"""
33
+ client = Mock()
34
+ client.execute = Mock()
35
+ client.vector_query = Mock()
36
+ client.vector_query.similarity_search = Mock()
37
+ return client
38
+
39
+ @pytest.fixture
40
+ def mock_index(self, mock_client):
41
+ """Create mock Pinecone-compatible index"""
42
+ index = PineconeCompatibleIndex(mock_client, "test_table", "embedding")
43
+ return index
44
+
45
+ def test_filter_parsing_empty_dict(self, mock_index):
46
+ """Test filter parsing with empty dictionary"""
47
+ where_conditions, where_params = mock_index._parse_pinecone_filter({})
48
+ assert where_conditions == []
49
+ assert where_params == []
50
+
51
+ def test_filter_parsing_none_value(self, mock_index):
52
+ """Test filter parsing with None value"""
53
+ where_conditions, where_params = mock_index._parse_pinecone_filter(None)
54
+ assert where_conditions == []
55
+ assert where_params == []
56
+
57
+ def test_filter_parsing_basic_equality(self, mock_index):
58
+ """Test filter parsing with basic equality"""
59
+ filter_dict = {"genre": "action"}
60
+ where_conditions, where_params = mock_index._parse_pinecone_filter(filter_dict)
61
+ assert where_conditions == ["genre = ?"]
62
+ assert where_params == ["action"]
63
+
64
+ def test_filter_parsing_operators(self, mock_index):
65
+ """Test filter parsing with various operators"""
66
+ # Test $eq
67
+ filter_dict = {"rating": {"$eq": 8.5}}
68
+ where_conditions, where_params = mock_index._parse_pinecone_filter(filter_dict)
69
+ assert where_conditions == ["rating = ?"]
70
+ assert where_params == [8.5]
71
+
72
+ # Test $ne
73
+ filter_dict = {"genre": {"$ne": "horror"}}
74
+ where_conditions, where_params = mock_index._parse_pinecone_filter(filter_dict)
75
+ assert where_conditions == ["genre != ?"]
76
+ assert where_params == ["horror"]
77
+
78
+ # Test $gt
79
+ filter_dict = {"year": {"$gt": 2000}}
80
+ where_conditions, where_params = mock_index._parse_pinecone_filter(filter_dict)
81
+ assert where_conditions == ["year > ?"]
82
+ assert where_params == [2000]
83
+
84
+ # Test $gte
85
+ filter_dict = {"rating": {"$gte": 8.0}}
86
+ where_conditions, where_params = mock_index._parse_pinecone_filter(filter_dict)
87
+ assert where_conditions == ["rating >= ?"]
88
+ assert where_params == [8.0]
89
+
90
+ # Test $lt
91
+ filter_dict = {"year": {"$lt": 2010}}
92
+ where_conditions, where_params = mock_index._parse_pinecone_filter(filter_dict)
93
+ assert where_conditions == ["year < ?"]
94
+ assert where_params == [2010]
95
+
96
+ # Test $lte
97
+ filter_dict = {"rating": {"$lte": 9.0}}
98
+ where_conditions, where_params = mock_index._parse_pinecone_filter(filter_dict)
99
+ assert where_conditions == ["rating <= ?"]
100
+ assert where_params == [9.0]
101
+
102
+ def test_filter_parsing_in_operator(self, mock_index):
103
+ """Test filter parsing with $in operator"""
104
+ filter_dict = {"genre": {"$in": ["action", "sci-fi", "drama"]}}
105
+ where_conditions, where_params = mock_index._parse_pinecone_filter(filter_dict)
106
+ assert where_conditions == ["genre IN (?,?,?)"]
107
+ assert where_params == ["action", "sci-fi", "drama"]
108
+
109
+ def test_filter_parsing_nin_operator(self, mock_index):
110
+ """Test filter parsing with $nin operator"""
111
+ filter_dict = {"genre": {"$nin": ["horror", "thriller"]}}
112
+ where_conditions, where_params = mock_index._parse_pinecone_filter(filter_dict)
113
+ assert where_conditions == ["genre NOT IN (?,?)"]
114
+ assert where_params == ["horror", "thriller"]
115
+
116
+ def test_filter_parsing_empty_in_list(self, mock_index):
117
+ """Test filter parsing with empty $in list"""
118
+ filter_dict = {"genre": {"$in": []}}
119
+ where_conditions, where_params = mock_index._parse_pinecone_filter(filter_dict)
120
+ assert where_conditions == ["1=0"] # Always false condition
121
+ assert where_params == []
122
+
123
+ def test_filter_parsing_empty_nin_list(self, mock_index):
124
+ """Test filter parsing with empty $nin list"""
125
+ filter_dict = {"genre": {"$nin": []}}
126
+ where_conditions, where_params = mock_index._parse_pinecone_filter(filter_dict)
127
+ assert where_conditions == ["1=1"] # Always true condition
128
+ assert where_params == []
129
+
130
+ def test_filter_parsing_and_operator(self, mock_index):
131
+ """Test filter parsing with $and operator"""
132
+ filter_dict = {"$and": [{"genre": "action"}, {"year": {"$gte": 2000}}]}
133
+ where_conditions, where_params = mock_index._parse_pinecone_filter(filter_dict)
134
+ assert where_conditions == ["(genre = ? AND year >= ?)"]
135
+ assert where_params == ["action", 2000]
136
+
137
+ def test_filter_parsing_or_operator(self, mock_index):
138
+ """Test filter parsing with $or operator"""
139
+ filter_dict = {"$or": [{"genre": "action"}, {"genre": "sci-fi"}]}
140
+ where_conditions, where_params = mock_index._parse_pinecone_filter(filter_dict)
141
+ assert where_conditions == ["(genre = ? OR genre = ?)"]
142
+ assert where_params == ["action", "sci-fi"]
143
+
144
+ def test_filter_parsing_nested_conditions(self, mock_index):
145
+ """Test filter parsing with nested $and and $or conditions"""
146
+ filter_dict = {
147
+ "$and": [
148
+ {"$or": [{"genre": "action"}, {"genre": "sci-fi"}]},
149
+ {"$and": [{"year": {"$gte": 2000}}, {"rating": {"$gte": 8.0}}]},
150
+ ]
151
+ }
152
+ where_conditions, where_params = mock_index._parse_pinecone_filter(filter_dict)
153
+ expected_condition = "((genre = ? OR genre = ?) AND (year >= ? AND rating >= ?))"
154
+ assert where_conditions == [expected_condition]
155
+ assert where_params == ["action", "sci-fi", 2000, 8.0]
156
+
157
+ def test_filter_parsing_special_characters(self, mock_index):
158
+ """Test filter parsing with special characters"""
159
+ filter_dict = {"director": "Director with apostrophe's name"}
160
+ where_conditions, where_params = mock_index._parse_pinecone_filter(filter_dict)
161
+ assert where_conditions == ["director = ?"]
162
+ assert where_params == ["Director with apostrophe's name"]
163
+
164
+ def test_filter_parsing_mixed_data_types(self, mock_index):
165
+ """Test filter parsing with mixed data types"""
166
+ filter_dict = {"year": {"$in": [1999, "2010", 2008]}}
167
+ where_conditions, where_params = mock_index._parse_pinecone_filter(filter_dict)
168
+ assert where_conditions == ["year IN (?,?,?)"]
169
+ assert where_params == [1999, "2010", 2008]
170
+
171
+ def test_filter_parsing_large_in_list(self, mock_index):
172
+ """Test filter parsing with large $in list"""
173
+ large_list = [str(i) for i in range(1000)]
174
+ large_list.extend(["action", "sci-fi"])
175
+ filter_dict = {"genre": {"$in": large_list}}
176
+ where_conditions, where_params = mock_index._parse_pinecone_filter(filter_dict)
177
+
178
+ # Check that placeholders are created correctly
179
+ expected_placeholders = ",".join(["?" for _ in range(1002)])
180
+ assert where_conditions == [f"genre IN ({expected_placeholders})"]
181
+ assert len(where_params) == 1002
182
+
183
+ def test_vector_dimension_validation(self, mock_index):
184
+ """Test vector dimension validation logic"""
185
+ # Test with valid vector
186
+ valid_vector = [0.1] * 64
187
+ # This should not raise an exception during parsing
188
+ where_conditions, where_params = mock_index._parse_pinecone_filter({"genre": "action"})
189
+ assert where_conditions == ["genre = ?"]
190
+ assert where_params == ["action"]
191
+
192
+ def test_top_k_validation_logic(self, mock_index):
193
+ """Test top_k validation logic"""
194
+ # Test various top_k values
195
+ test_cases = [0, 1, 10, 100, 10000]
196
+ for top_k in test_cases:
197
+ # The logic should handle any positive integer
198
+ assert isinstance(top_k, int)
199
+ assert top_k >= 0
200
+
201
+ def test_query_response_structure(self):
202
+ """Test QueryResponse structure validation"""
203
+ # Create mock matches
204
+ matches = [
205
+ VectorMatch(
206
+ id="1",
207
+ score=0.1,
208
+ metadata={"title": "Movie 1", "genre": "action"},
209
+ values=[0.1] * 64,
210
+ ),
211
+ VectorMatch(
212
+ id="2",
213
+ score=0.2,
214
+ metadata={"title": "Movie 2", "genre": "sci-fi"},
215
+ values=[0.2] * 64,
216
+ ),
217
+ ]
218
+
219
+ response = QueryResponse(matches=matches, namespace="test", usage={"read_units": 2})
220
+
221
+ # Validate structure
222
+ assert len(response.matches) == 2
223
+ assert response.namespace == "test"
224
+ assert response.usage["read_units"] == 2
225
+
226
+ # Validate match structure
227
+ for match in response.matches:
228
+ assert hasattr(match, 'id')
229
+ assert hasattr(match, 'score')
230
+ assert hasattr(match, 'metadata')
231
+ assert hasattr(match, 'values')
232
+ assert isinstance(match.score, (int, float))
233
+ assert match.score >= 0
234
+
235
+ def test_vector_match_structure(self):
236
+ """Test VectorMatch structure validation"""
237
+ match = VectorMatch(id="test_id", score=0.5, metadata={"key": "value"}, values=[0.1, 0.2, 0.3])
238
+
239
+ assert match.id == "test_id"
240
+ assert match.score == 0.5
241
+ assert match.metadata == {"key": "value"}
242
+ assert match.values == [0.1, 0.2, 0.3]
243
+
244
+ def test_result_ordering_logic(self):
245
+ """Test result ordering logic validation"""
246
+ # Create matches with different scores
247
+ matches = [
248
+ VectorMatch(id="1", score=0.3, metadata={}, values=None),
249
+ VectorMatch(id="2", score=0.1, metadata={}, values=None),
250
+ VectorMatch(id="3", score=0.2, metadata={}, values=None),
251
+ ]
252
+
253
+ # Sort by score (ascending for distance)
254
+ sorted_matches = sorted(matches, key=lambda x: x.score)
255
+
256
+ # Validate ordering
257
+ assert sorted_matches[0].score == 0.1
258
+ assert sorted_matches[1].score == 0.2
259
+ assert sorted_matches[2].score == 0.3
260
+
261
+ def test_usage_statistics_logic(self):
262
+ """Test usage statistics logic validation"""
263
+ # Test various scenarios
264
+ test_cases = [
265
+ (0, 0), # top_k=0, expected_read_units=0
266
+ (1, 1), # top_k=1, expected_read_units=1
267
+ (5, 5), # top_k=5, expected_read_units=5
268
+ (100, 5), # top_k=100, but only 5 records available
269
+ ]
270
+
271
+ for top_k, expected_read_units in test_cases:
272
+ # Simulate the logic
273
+ actual_read_units = min(top_k, 5) # Assuming 5 records available
274
+ assert actual_read_units == expected_read_units
275
+
276
+ def test_namespace_consistency_logic(self):
277
+ """Test namespace consistency logic validation"""
278
+ test_namespaces = ["", "default", "test_namespace", "namespace_with_underscores"]
279
+
280
+ for namespace in test_namespaces:
281
+ # Namespace should be preserved as-is
282
+ assert isinstance(namespace, str)
283
+ # Should be able to handle any string value
284
+ processed_namespace = namespace
285
+ assert processed_namespace == namespace
286
+
287
+ def test_include_parameters_logic(self):
288
+ """Test include_metadata and include_values parameter logic"""
289
+ # Test all combinations
290
+ test_cases = [
291
+ (True, True), # include both
292
+ (True, False), # include metadata only
293
+ (False, True), # include values only
294
+ (False, False), # include neither
295
+ ]
296
+
297
+ for include_metadata, include_values in test_cases:
298
+ # Validate parameter types
299
+ assert isinstance(include_metadata, bool)
300
+ assert isinstance(include_values, bool)
301
+
302
+ # Test logic combinations
303
+ if include_metadata and include_values:
304
+ # Both included
305
+ pass
306
+ elif include_metadata and not include_values:
307
+ # Metadata only
308
+ pass
309
+ elif not include_metadata and include_values:
310
+ # Values only
311
+ pass
312
+ else:
313
+ # Neither included
314
+ pass
315
+
316
+ def test_vector_value_handling_logic(self):
317
+ """Test vector value handling logic"""
318
+ # Test different vector representations
319
+ test_vectors = [
320
+ [0.1] * 64, # List of floats
321
+ [0.0] * 64, # Zero vector
322
+ [-0.1] * 64, # Negative values
323
+ [100.0] * 64, # Large values
324
+ [0.1, 0.2, 0.3], # Short vector (should be handled)
325
+ ]
326
+
327
+ for vector in test_vectors:
328
+ # Validate vector structure
329
+ assert isinstance(vector, list)
330
+ assert all(isinstance(x, (int, float)) for x in vector)
331
+
332
+ # Test vector string conversion logic
333
+ vector_str = "[" + ",".join(map(str, vector)) + "]"
334
+ assert vector_str.startswith("[")
335
+ assert vector_str.endswith("]")
336
+
337
+ def test_error_handling_logic(self):
338
+ """Test error handling logic for various edge cases"""
339
+ # Test unsupported operators
340
+ with pytest.raises(ValueError, match="Unsupported operator"):
341
+ mock_index = PineconeCompatibleIndex(Mock(), "test", "embedding")
342
+ mock_index._parse_pinecone_filter({"field": {"$unsupported": "value"}})
343
+
344
+ def test_consistency_logic(self, mock_index):
345
+ """Test consistency logic for multiple operations"""
346
+ # Test that same input produces same output
347
+ filter_dict = {"genre": "action", "year": {"$gte": 2000}}
348
+
349
+ # Parse multiple times
350
+ results = []
351
+ for _ in range(3):
352
+ where_conditions, where_params = mock_index._parse_pinecone_filter(filter_dict)
353
+ results.append((where_conditions, where_params))
354
+
355
+ # All results should be identical
356
+ for i in range(1, len(results)):
357
+ assert results[i] == results[0]
358
+
359
+ def test_edge_case_handling(self, mock_index):
360
+ """Test edge case handling logic"""
361
+ # Test with very large numbers
362
+ large_number = 999999999
363
+ filter_dict = {"id": {"$eq": large_number}}
364
+ where_conditions, where_params = mock_index._parse_pinecone_filter(filter_dict)
365
+ assert where_conditions == ["id = ?"]
366
+ assert where_params == [large_number]
367
+
368
+ # Test with very small numbers
369
+ small_number = 0.000001
370
+ filter_dict = {"rating": {"$eq": small_number}}
371
+ where_conditions, where_params = mock_index._parse_pinecone_filter(filter_dict)
372
+ assert where_conditions == ["rating = ?"]
373
+ assert where_params == [small_number]
374
+
375
+
376
+ if __name__ == "__main__":
377
+ pytest.main([__file__])