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,609 @@
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 MatrixOne dialect schema handling.
17
+ Tests the has_table method and type compiler functionality.
18
+ """
19
+
20
+ import pytest
21
+ from unittest.mock import Mock, patch, MagicMock
22
+ from sqlalchemy import create_engine, MetaData, Table, Column, Integer, String
23
+ from sqlalchemy.orm import declarative_base
24
+ from sqlalchemy.exc import SQLAlchemyError
25
+
26
+ from matrixone.sqlalchemy_ext.dialect import MatrixOneDialect, MatrixOneTypeCompiler
27
+
28
+
29
+ class TestMatrixOneDialectSchemaHandling:
30
+ """Test MatrixOne dialect schema handling functionality."""
31
+
32
+ def setup_method(self):
33
+ """Set up test fixtures."""
34
+ self.dialect = MatrixOneDialect()
35
+ self.mock_connection = Mock()
36
+ self.mock_connection.connection = Mock()
37
+
38
+ def test_has_table_with_none_schema(self):
39
+ """Test has_table method with schema=None (the main issue we're fixing)."""
40
+ # Mock connection with database name in DSN
41
+ self.mock_connection.connection.dsn = "host=localhost port=6001 dbname=test user=root"
42
+ # Mock get_dsn_parameters to return None so it falls back to DSN parsing
43
+ self.mock_connection.connection.get_dsn_parameters.return_value = {}
44
+
45
+ # Mock the super().has_table call to return True
46
+ with patch('sqlalchemy.dialects.mysql.base.MySQLDialect.has_table') as mock_parent_has_table:
47
+ mock_parent_has_table.return_value = True
48
+
49
+ # Test the method
50
+ result = self.dialect.has_table(self.mock_connection, "test_table", schema=None)
51
+
52
+ # Verify that schema was set to "test" (from DSN)
53
+ mock_parent_has_table.assert_called_once_with(self.mock_connection, "test_table", "test", **{})
54
+ assert result is True
55
+
56
+ def test_has_table_with_explicit_schema(self):
57
+ """Test has_table method with explicit schema."""
58
+ # Mock the super().has_table call
59
+ with patch('sqlalchemy.dialects.mysql.base.MySQLDialect.has_table') as mock_parent_has_table:
60
+ mock_parent_has_table.return_value = False
61
+
62
+ # Test with explicit schema
63
+ result = self.dialect.has_table(self.mock_connection, "test_table", schema="mydb")
64
+
65
+ # Verify that the explicit schema was used
66
+ mock_parent_has_table.assert_called_once_with(self.mock_connection, "test_table", "mydb", **{})
67
+ assert result is False
68
+
69
+ def test_has_table_fallback_to_default_schema(self):
70
+ """Test has_table method fallback to default schema when DSN parsing fails."""
71
+ # Mock connection without DSN or with invalid DSN
72
+ self.mock_connection.connection.dsn = "invalid_dsn"
73
+ # Mock get_dsn_parameters to return None so it falls back to DSN parsing
74
+ self.mock_connection.connection.get_dsn_parameters.return_value = {}
75
+
76
+ with patch('sqlalchemy.dialects.mysql.base.MySQLDialect.has_table') as mock_parent_has_table:
77
+ mock_parent_has_table.return_value = True
78
+
79
+ # Test the method
80
+ result = self.dialect.has_table(self.mock_connection, "test_table", schema=None)
81
+
82
+ # Verify that fallback schema "test" was used
83
+ mock_parent_has_table.assert_called_once_with(self.mock_connection, "test_table", "test", **{})
84
+ assert result is True
85
+
86
+ def test_has_table_with_dsn_parameters(self):
87
+ """Test has_table method with DSN parameters."""
88
+ # Mock connection with get_dsn_parameters method
89
+ self.mock_connection.connection.get_dsn_parameters.return_value = {
90
+ 'dbname': 'mydatabase',
91
+ 'user': 'root',
92
+ 'host': 'localhost',
93
+ }
94
+
95
+ with patch('sqlalchemy.dialects.mysql.base.MySQLDialect.has_table') as mock_parent_has_table:
96
+ mock_parent_has_table.return_value = True
97
+
98
+ # Test the method
99
+ result = self.dialect.has_table(self.mock_connection, "test_table", schema=None)
100
+
101
+ # Verify that database name from DSN parameters was used
102
+ mock_parent_has_table.assert_called_once_with(self.mock_connection, "test_table", "mydatabase", **{})
103
+ assert result is True
104
+
105
+ def test_has_table_exception_handling(self):
106
+ """Test has_table method exception handling."""
107
+ # Mock connection that raises exception
108
+ self.mock_connection.connection.dsn = "invalid_dsn"
109
+ self.mock_connection.connection.get_dsn_parameters.side_effect = Exception("Connection error")
110
+
111
+ with patch('sqlalchemy.dialects.mysql.base.MySQLDialect.has_table') as mock_parent_has_table:
112
+ mock_parent_has_table.return_value = True
113
+
114
+ # Test the method
115
+ result = self.dialect.has_table(self.mock_connection, "test_table", schema=None)
116
+
117
+ # Verify that fallback schema "test" was used after exception
118
+ mock_parent_has_table.assert_called_once_with(self.mock_connection, "test_table", "test", **{})
119
+ assert result is True
120
+
121
+
122
+ class TestMatrixOneTypeCompiler:
123
+ """Test MatrixOne type compiler functionality."""
124
+
125
+ def setup_method(self):
126
+ """Set up test fixtures."""
127
+ self.dialect = MatrixOneDialect()
128
+ self.type_compiler = MatrixOneTypeCompiler(self.dialect)
129
+
130
+ def test_visit_vector_type(self):
131
+ """Test visit_VECTOR method."""
132
+ # Mock vector type
133
+ mock_vector_type = Mock()
134
+ mock_vector_type.get_col_spec.return_value = "VECTOR(128)"
135
+
136
+ result = self.type_compiler.visit_VECTOR(mock_vector_type)
137
+ assert result == "VECTOR(128)"
138
+
139
+ def test_visit_vector_type_without_get_col_spec(self):
140
+ """Test visit_VECTOR method without get_col_spec."""
141
+ # Mock vector type without get_col_spec
142
+ mock_vector_type = Mock()
143
+ del mock_vector_type.get_col_spec
144
+
145
+ result = self.type_compiler.visit_VECTOR(mock_vector_type)
146
+ assert result == "VECTOR"
147
+
148
+ def test_visit_integer_type(self):
149
+ """Test visit_integer method delegation to MySQL compiler."""
150
+ from sqlalchemy import Integer
151
+
152
+ # Test the method directly - it should work without mocking
153
+ result = self.type_compiler.visit_integer(Integer())
154
+
155
+ # Verify it returns a valid SQL type
156
+ assert result == "INTEGER"
157
+
158
+ def test_visit_string_type(self):
159
+ """Test visit_string method delegation to MySQL compiler."""
160
+ from sqlalchemy import String
161
+
162
+ # Test the method directly - it should work without mocking
163
+ result = self.type_compiler.visit_string(String(255))
164
+
165
+ # Verify it returns a valid SQL type
166
+ assert result == "VARCHAR(255)"
167
+
168
+ def test_visit_text_type(self):
169
+ """Test visit_text method delegation to MySQL compiler."""
170
+ from sqlalchemy import Text
171
+
172
+ # Test the method directly - it should work without mocking
173
+ result = self.type_compiler.visit_text(Text())
174
+
175
+ # Verify it returns a valid SQL type
176
+ assert result == "TEXT"
177
+
178
+ def test_visit_boolean_type(self):
179
+ """Test visit_boolean method delegation to MySQL compiler."""
180
+ from sqlalchemy import Boolean
181
+
182
+ # Test the method directly - it should work without mocking
183
+ result = self.type_compiler.visit_boolean(Boolean())
184
+
185
+ # Verify it returns a valid SQL type
186
+ assert result == "BOOL"
187
+
188
+ def test_visit_float_type(self):
189
+ """Test visit_float method delegation to MySQL compiler."""
190
+ from sqlalchemy import Float
191
+
192
+ # Test the method directly - it should work without mocking
193
+ result = self.type_compiler.visit_float(Float())
194
+
195
+ # Verify it returns a valid SQL type
196
+ assert result == "FLOAT"
197
+
198
+ def test_visit_numeric_type(self):
199
+ """Test visit_numeric method delegation to MySQL compiler."""
200
+ from sqlalchemy import Numeric
201
+ import sqlalchemy
202
+
203
+ # Test the method directly - it should work without mocking
204
+ result = self.type_compiler.visit_numeric(Numeric(10, 2))
205
+
206
+ # Verify it returns a valid SQL type
207
+ # SQLAlchemy 2.0.x delegates to MySQL and returns NUMERIC
208
+ # SQLAlchemy 1.4.x has explicit implementation that returns DECIMAL
209
+ if sqlalchemy.__version__.startswith('2.'):
210
+ expected = "NUMERIC(10, 2)"
211
+ else:
212
+ expected = "DECIMAL(10, 2)"
213
+
214
+ assert result == expected
215
+
216
+ def test_visit_date_type(self):
217
+ """Test visit_date method delegation to MySQL compiler."""
218
+ from sqlalchemy import Date
219
+
220
+ # Test the method directly - it should work without mocking
221
+ result = self.type_compiler.visit_date(Date())
222
+
223
+ # Verify it returns a valid SQL type
224
+ assert result == "DATE"
225
+
226
+ def test_visit_datetime_type(self):
227
+ """Test visit_datetime method delegation to MySQL compiler."""
228
+ from sqlalchemy import DateTime
229
+
230
+ # Test the method directly - it should work without mocking
231
+ result = self.type_compiler.visit_datetime(DateTime())
232
+
233
+ # Verify it returns a valid SQL type
234
+ assert result == "DATETIME"
235
+
236
+ def test_visit_time_type(self):
237
+ """Test visit_time method delegation to MySQL compiler."""
238
+ from sqlalchemy import Time
239
+
240
+ # Test the method directly - it should work without mocking
241
+ result = self.type_compiler.visit_time(Time())
242
+
243
+ # Verify it returns a valid SQL type
244
+ assert result == "TIME"
245
+
246
+ def test_visit_timestamp_type(self):
247
+ """Test visit_timestamp method delegation to MySQL compiler."""
248
+ from sqlalchemy import TIMESTAMP
249
+
250
+ # Test the method directly - it should work without mocking
251
+ result = self.type_compiler.visit_timestamp(TIMESTAMP())
252
+
253
+ # Verify it returns a valid SQL type
254
+ assert result == "TIMESTAMP"
255
+
256
+ def test_visit_decimal_type(self):
257
+ """Test visit_DECIMAL method for SQLAlchemy 2.0 compatibility."""
258
+ from sqlalchemy import DECIMAL
259
+
260
+ # Test DECIMAL with precision and scale
261
+ decimal_type = DECIMAL(10, 2)
262
+ result = self.type_compiler.visit_DECIMAL(decimal_type)
263
+ assert result == "DECIMAL(10, 2)"
264
+
265
+ # Test DECIMAL without precision and scale
266
+ decimal_type = DECIMAL()
267
+ result = self.type_compiler.visit_DECIMAL(decimal_type)
268
+ assert result == "DECIMAL"
269
+
270
+ def test_visit_numeric_type_new(self):
271
+ """Test visit_NUMERIC method for SQLAlchemy 2.0 compatibility."""
272
+ from sqlalchemy import NUMERIC
273
+
274
+ # Test NUMERIC with precision and scale
275
+ numeric_type = NUMERIC(15, 3)
276
+ result = self.type_compiler.visit_NUMERIC(numeric_type)
277
+ assert result == "DECIMAL(15, 3)"
278
+
279
+ # Test NUMERIC without precision and scale
280
+ numeric_type = NUMERIC()
281
+ result = self.type_compiler.visit_NUMERIC(numeric_type)
282
+ assert result == "DECIMAL"
283
+
284
+ def test_visit_bigint_type(self):
285
+ """Test visit_BIGINT method for SQLAlchemy 2.0 compatibility."""
286
+ from sqlalchemy import BigInteger
287
+
288
+ # Test BIGINT type
289
+ bigint_type = BigInteger()
290
+ result = self.type_compiler.visit_BIGINT(bigint_type)
291
+ assert result == "BIGINT"
292
+
293
+ def test_visit_big_integer_type(self):
294
+ """Test visit_big_integer method for SQLAlchemy 2.0 compatibility."""
295
+ from sqlalchemy import BigInteger
296
+
297
+ # Test BigInteger type
298
+ bigint_type = BigInteger()
299
+ result = self.type_compiler.visit_big_integer(bigint_type)
300
+ assert result == "BIGINT"
301
+
302
+ def test_visit_timestamp_type_new(self):
303
+ """Test visit_TIMESTAMP method for SQLAlchemy 2.0 compatibility."""
304
+ from sqlalchemy import TIMESTAMP
305
+
306
+ # Test TIMESTAMP type
307
+ timestamp_type = TIMESTAMP()
308
+ result = self.type_compiler.visit_TIMESTAMP(timestamp_type)
309
+ assert result == "TIMESTAMP"
310
+
311
+ # Additional comprehensive type compatibility tests
312
+ def test_visit_small_integer_type(self):
313
+ """Test visit_small_integer method delegation to MySQL compiler."""
314
+ from sqlalchemy import SmallInteger
315
+
316
+ result = self.type_compiler.visit_small_integer(SmallInteger())
317
+ assert result == "SMALLINT"
318
+
319
+ def test_visit_big_integer_type(self):
320
+ """Test visit_big_integer method delegation to MySQL compiler."""
321
+ from sqlalchemy import BigInteger
322
+
323
+ result = self.type_compiler.visit_big_integer(BigInteger())
324
+ assert result == "BIGINT"
325
+
326
+ def test_visit_char_type(self):
327
+ """Test visit_CHAR method delegation to MySQL compiler."""
328
+ from sqlalchemy import CHAR
329
+
330
+ result = self.type_compiler.visit_CHAR(CHAR(10))
331
+ assert result == "CHAR(10)"
332
+
333
+ def test_visit_varchar_type(self):
334
+ """Test visit_VARCHAR method delegation to MySQL compiler."""
335
+ from sqlalchemy import VARCHAR
336
+
337
+ result = self.type_compiler.visit_VARCHAR(VARCHAR(255))
338
+ assert result == "VARCHAR(255)"
339
+
340
+ def test_visit_unicode_type(self):
341
+ """Test visit_unicode method delegation to MySQL compiler."""
342
+ from sqlalchemy import Unicode
343
+
344
+ result = self.type_compiler.visit_unicode(Unicode(100))
345
+ assert result == "VARCHAR(100)"
346
+
347
+ def test_visit_unicode_text_type(self):
348
+ """Test visit_unicode_text method delegation to MySQL compiler."""
349
+ from sqlalchemy import UnicodeText
350
+
351
+ result = self.type_compiler.visit_unicode_text(UnicodeText())
352
+ assert result == "TEXT"
353
+
354
+ def test_visit_large_binary_type(self):
355
+ """Test visit_large_binary method delegation to MySQL compiler."""
356
+ from sqlalchemy import LargeBinary
357
+
358
+ result = self.type_compiler.visit_large_binary(LargeBinary())
359
+ assert result == "BLOB" # MySQL returns BLOB for LargeBinary
360
+
361
+ def test_visit_binary_type(self):
362
+ """Test visit_binary method delegation to MySQL compiler."""
363
+ from sqlalchemy import BINARY
364
+
365
+ result = self.type_compiler.visit_binary(BINARY(100))
366
+ assert result == "BINARY(100)"
367
+
368
+ def test_visit_blob_type(self):
369
+ """Test visit_BLOB method delegation to MySQL compiler."""
370
+ from sqlalchemy import BLOB
371
+
372
+ result = self.type_compiler.visit_BLOB(BLOB())
373
+ assert result == "BLOB"
374
+
375
+ def test_visit_enum_type(self):
376
+ """Test visit_enum method delegation to MySQL compiler."""
377
+ from sqlalchemy import Enum
378
+
379
+ result = self.type_compiler.visit_enum(Enum('A', 'B', 'C'))
380
+ assert result == "ENUM('A','B','C')"
381
+
382
+ def test_visit_pickle_type(self):
383
+ """Test visit_pickle method delegation to MySQL compiler."""
384
+ from sqlalchemy import PickleType
385
+
386
+ # PickleType is handled by the parent MySQL compiler
387
+ # We need to test it through the process method
388
+ result = self.type_compiler.process(PickleType())
389
+ assert result == "BLOB"
390
+
391
+ def test_visit_interval_type(self):
392
+ """Test visit_interval method delegation to MySQL compiler."""
393
+ from sqlalchemy import Interval
394
+
395
+ # Interval is handled by the parent MySQL compiler
396
+ # We need to test it through the process method
397
+ result = self.type_compiler.process(Interval())
398
+ assert result == "DATETIME" # MySQL returns DATETIME for Interval
399
+
400
+ def test_visit_uuid_type(self):
401
+ """Test visit_uuid method delegation to MySQL compiler."""
402
+ try:
403
+ from sqlalchemy import UUID
404
+
405
+ result = self.type_compiler.visit_uuid(UUID())
406
+ assert result == "CHAR(32)" # MySQL returns CHAR(32) for UUID
407
+ except ImportError:
408
+ # UUID type not available in this SQLAlchemy version
409
+ pass
410
+
411
+ def test_visit_json_type(self):
412
+ """Test visit_JSON method delegation to MySQL compiler."""
413
+ try:
414
+ from sqlalchemy import JSON
415
+
416
+ result = self.type_compiler.visit_JSON(JSON())
417
+ assert result == "JSON"
418
+ except ImportError:
419
+ # JSON type not available in this SQLAlchemy version
420
+ pytest.skip("JSON type not available in this SQLAlchemy version")
421
+
422
+ def test_visit_arbitrary_type(self):
423
+ """Test visit_arbitrary_type method delegation to MySQL compiler."""
424
+ from sqlalchemy import TypeDecorator, String
425
+
426
+ class CustomType(TypeDecorator):
427
+ impl = String(255) # Provide length to avoid VARCHAR length error
428
+
429
+ # Test through process method since visit_arbitrary_type may not exist
430
+ result = self.type_compiler.process(CustomType())
431
+ assert result == "VARCHAR(255)"
432
+
433
+ def test_visit_type_decorator_type(self):
434
+ """Test visit_type_decorator method delegation to MySQL compiler."""
435
+ from sqlalchemy import TypeDecorator, String
436
+
437
+ class CustomType(TypeDecorator):
438
+ impl = String(255) # Provide length to avoid VARCHAR length error
439
+
440
+ result = self.type_compiler.visit_type_decorator(CustomType())
441
+ assert result == "VARCHAR(255)"
442
+
443
+ def test_visit_user_defined_type(self):
444
+ """Test visit_user_defined_type method delegation to MySQL compiler."""
445
+ from sqlalchemy import TypeDecorator, String
446
+
447
+ class CustomType(TypeDecorator):
448
+ impl = String(255) # Provide length to avoid VARCHAR length error
449
+
450
+ # Test through process method since visit_user_defined_type may not exist
451
+ result = self.type_compiler.process(CustomType())
452
+ assert result == "VARCHAR(255)"
453
+
454
+ # Test type compatibility with different SQLAlchemy versions
455
+ def test_type_compatibility_matrix(self):
456
+ """Test type compatibility across different SQLAlchemy versions."""
457
+ import sqlalchemy
458
+ from sqlalchemy import Integer, String, Numeric, DateTime
459
+
460
+ # Test basic types that should work consistently
461
+ test_cases = [
462
+ (Integer(), "INTEGER"),
463
+ (String(255), "VARCHAR(255)"),
464
+ (DateTime(), "DATETIME"),
465
+ ]
466
+
467
+ for sql_type, expected in test_cases:
468
+ # Get the appropriate visit method name
469
+ type_name = sql_type.__class__.__name__.lower()
470
+ visit_method = getattr(self.type_compiler, f"visit_{type_name}", None)
471
+
472
+ if visit_method:
473
+ result = visit_method(sql_type)
474
+ assert result == expected, f"Type {type_name} failed: expected {expected}, got {result}"
475
+
476
+ def test_numeric_precision_scale_compatibility(self):
477
+ """Test numeric types with different precision and scale values."""
478
+ import sqlalchemy
479
+ from sqlalchemy import Numeric, DECIMAL, NUMERIC
480
+
481
+ # Test cases with different precision and scale
482
+ test_cases = [
483
+ (
484
+ Numeric(5, 2),
485
+ "DECIMAL(5, 2)" if sqlalchemy.__version__.startswith('1.') else "NUMERIC(5, 2)",
486
+ ),
487
+ (
488
+ Numeric(10, 0),
489
+ "DECIMAL(10, 0)" if sqlalchemy.__version__.startswith('1.') else "NUMERIC(10, 0)",
490
+ ),
491
+ (
492
+ Numeric(18, 4),
493
+ "DECIMAL(18, 4)" if sqlalchemy.__version__.startswith('1.') else "NUMERIC(18, 4)",
494
+ ),
495
+ ]
496
+
497
+ for sql_type, expected in test_cases:
498
+ result = self.type_compiler.visit_numeric(sql_type)
499
+ assert result == expected, f"Numeric type failed: expected {expected}, got {result}"
500
+
501
+ def test_string_length_compatibility(self):
502
+ """Test string types with different length specifications."""
503
+ from sqlalchemy import String, VARCHAR, CHAR
504
+
505
+ test_cases = [
506
+ (String(1), "VARCHAR(1)"),
507
+ (String(255), "VARCHAR(255)"),
508
+ (String(65535), "VARCHAR(65535)"),
509
+ (VARCHAR(100), "VARCHAR(100)"),
510
+ (CHAR(10), "CHAR(10)"),
511
+ ]
512
+
513
+ for sql_type, expected in test_cases:
514
+ # Use the process method which handles all types correctly
515
+ result = self.type_compiler.process(sql_type)
516
+ assert result == expected, f"String type {sql_type.__class__.__name__} failed: expected {expected}, got {result}"
517
+
518
+ def test_datetime_precision_compatibility(self):
519
+ """Test datetime types with different precision specifications."""
520
+ from sqlalchemy import DateTime, TIMESTAMP, Time
521
+
522
+ test_cases = [
523
+ (DateTime(), "DATETIME"),
524
+ (TIMESTAMP(), "TIMESTAMP"),
525
+ (Time(), "TIME"),
526
+ ]
527
+
528
+ for sql_type, expected in test_cases:
529
+ type_name = sql_type.__class__.__name__.lower()
530
+ visit_method = getattr(self.type_compiler, f"visit_{type_name}")
531
+ result = visit_method(sql_type)
532
+ assert result == expected, f"DateTime type {type_name} failed: expected {expected}, got {result}"
533
+
534
+
535
+ class TestMatrixOneDialectIntegration:
536
+ """Test MatrixOne dialect integration with SQLAlchemy components."""
537
+
538
+ def test_dialect_registration(self):
539
+ """Test that MatrixOne dialect is properly registered."""
540
+ # Test that the dialect has the correct name
541
+ assert MatrixOneDialect.name == "matrixone"
542
+
543
+ # Test that the dialect has the required components
544
+ assert hasattr(MatrixOneDialect, 'statement_compiler')
545
+ assert hasattr(MatrixOneDialect, 'type_compiler')
546
+ assert MatrixOneDialect.statement_compiler is not None
547
+ assert MatrixOneDialect.type_compiler is not None
548
+
549
+ def test_dialect_initialization(self):
550
+ """Test MatrixOne dialect initialization."""
551
+ dialect = MatrixOneDialect()
552
+
553
+ # Test that the dialect initializes correctly
554
+ assert dialect.name == "matrixone"
555
+ assert hasattr(dialect, '_connection_charset')
556
+ assert dialect._connection_charset == "utf8mb4"
557
+ assert dialect.supports_statement_cache is True
558
+
559
+ def test_dialect_error_extraction(self):
560
+ """Test error code extraction from MatrixOne exceptions."""
561
+ dialect = MatrixOneDialect()
562
+
563
+ # Test with integer error code
564
+ mock_exception = Mock()
565
+ mock_exception.args = (1062, "Duplicate entry")
566
+ error_code = dialect._extract_error_code(mock_exception)
567
+ assert error_code == 1062
568
+
569
+ # Test with non-integer error code
570
+ mock_exception.args = ("Error message",)
571
+ error_code = dialect._extract_error_code(mock_exception)
572
+ assert error_code is None
573
+
574
+ # Test with empty args
575
+ mock_exception.args = ()
576
+ error_code = dialect._extract_error_code(mock_exception)
577
+ assert error_code is None
578
+
579
+ def test_dialect_table_names(self):
580
+ """Test get_table_names method."""
581
+ dialect = MatrixOneDialect()
582
+ mock_connection = Mock()
583
+
584
+ # Mock the parent method
585
+ with patch('sqlalchemy.dialects.mysql.base.MySQLDialect.get_table_names') as mock_parent_get_table_names:
586
+ mock_parent_get_table_names.return_value = ["table1", "table2"]
587
+
588
+ # Test the method
589
+ result = dialect.get_table_names(mock_connection, schema="test")
590
+
591
+ # Verify that the parent method was called
592
+ mock_parent_get_table_names.assert_called_once_with(mock_connection, "test", **{})
593
+ assert result == ["table1", "table2"]
594
+
595
+ def test_dialect_columns(self):
596
+ """Test get_columns method."""
597
+ dialect = MatrixOneDialect()
598
+ mock_connection = Mock()
599
+
600
+ # Mock the parent method
601
+ with patch('sqlalchemy.dialects.mysql.base.MySQLDialect.get_columns') as mock_parent_get_columns:
602
+ mock_parent_get_columns.return_value = [{"name": "id", "type": "INT"}]
603
+
604
+ # Test the method
605
+ result = dialect.get_columns(mock_connection, "test_table", schema="test")
606
+
607
+ # Verify that the parent method was called
608
+ mock_parent_get_columns.assert_called_once_with(mock_connection, "test_table", "test", **{})
609
+ assert result == [{"name": "id", "type": "INT"}]