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,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"}]
|