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,300 @@
|
|
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
|
+
Test async transaction simple_query functionality
|
17
|
+
"""
|
18
|
+
|
19
|
+
import pytest
|
20
|
+
import pytest_asyncio
|
21
|
+
from matrixone import AsyncClient
|
22
|
+
from matrixone.sqlalchemy_ext import boolean_match, natural_match
|
23
|
+
|
24
|
+
|
25
|
+
class TestAsyncTransactionSimpleQuery:
|
26
|
+
"""Test async transaction simple_query operations"""
|
27
|
+
|
28
|
+
@pytest_asyncio.fixture(scope="function")
|
29
|
+
async def async_client_setup(self):
|
30
|
+
"""Setup async client for testing"""
|
31
|
+
client = AsyncClient()
|
32
|
+
await client.connect("127.0.0.1", 6001, "root", "111", "test")
|
33
|
+
|
34
|
+
# Enable fulltext indexing
|
35
|
+
await client.execute("SET experimental_fulltext_index=1")
|
36
|
+
|
37
|
+
# Create test database and table
|
38
|
+
test_db = "async_tx_simple_query_test"
|
39
|
+
await client.execute(f"CREATE DATABASE IF NOT EXISTS {test_db}")
|
40
|
+
await client.execute(f"USE {test_db}")
|
41
|
+
|
42
|
+
await client.execute("DROP TABLE IF EXISTS async_tx_docs")
|
43
|
+
await client.execute(
|
44
|
+
"""
|
45
|
+
CREATE TABLE async_tx_docs (
|
46
|
+
id INT AUTO_INCREMENT PRIMARY KEY,
|
47
|
+
title VARCHAR(255) NOT NULL,
|
48
|
+
content TEXT NOT NULL,
|
49
|
+
category VARCHAR(100) NOT NULL
|
50
|
+
)
|
51
|
+
"""
|
52
|
+
)
|
53
|
+
|
54
|
+
# Insert test data
|
55
|
+
test_docs = [
|
56
|
+
(
|
57
|
+
"Python Async Programming",
|
58
|
+
"Learn Python async programming with asyncio",
|
59
|
+
"Programming",
|
60
|
+
),
|
61
|
+
(
|
62
|
+
"JavaScript Async Patterns",
|
63
|
+
"Modern JavaScript async patterns and promises",
|
64
|
+
"Programming",
|
65
|
+
),
|
66
|
+
(
|
67
|
+
"Database Async Operations",
|
68
|
+
"Async database operations and connection pooling",
|
69
|
+
"Database",
|
70
|
+
),
|
71
|
+
("Web Async Development", "Async web development with modern frameworks", "Web"),
|
72
|
+
(
|
73
|
+
"Async Testing Strategies",
|
74
|
+
"Testing async code and handling async test cases",
|
75
|
+
"Testing",
|
76
|
+
),
|
77
|
+
]
|
78
|
+
|
79
|
+
for title, content, category in test_docs:
|
80
|
+
await client.execute(
|
81
|
+
f"""
|
82
|
+
INSERT INTO async_tx_docs (title, content, category)
|
83
|
+
VALUES ('{title}', '{content}', '{category}')
|
84
|
+
"""
|
85
|
+
)
|
86
|
+
|
87
|
+
# Create fulltext index
|
88
|
+
await client.fulltext_index.create("async_tx_docs", "ftidx_async_tx", ["title", "content"])
|
89
|
+
|
90
|
+
yield client, test_db
|
91
|
+
|
92
|
+
# Cleanup
|
93
|
+
try:
|
94
|
+
await client.fulltext_index.drop("async_tx_docs", "ftidx_async_tx")
|
95
|
+
await client.execute("DROP TABLE async_tx_docs")
|
96
|
+
await client.execute(f"DROP DATABASE {test_db}")
|
97
|
+
except Exception as e:
|
98
|
+
print(f"Cleanup warning: {e}")
|
99
|
+
finally:
|
100
|
+
await client.disconnect()
|
101
|
+
|
102
|
+
@pytest.mark.asyncio
|
103
|
+
async def test_async_transaction_simple_query_basic(self, async_client_setup):
|
104
|
+
"""Test basic async transaction simple_query functionality"""
|
105
|
+
client, test_db = async_client_setup
|
106
|
+
|
107
|
+
async with client.transaction() as tx:
|
108
|
+
# Test basic search within transaction
|
109
|
+
result = await tx.query("async_tx_docs").filter(boolean_match("title", "content").encourage("python")).execute()
|
110
|
+
|
111
|
+
assert result is not None
|
112
|
+
rows = result.fetchall()
|
113
|
+
assert len(rows) > 0
|
114
|
+
|
115
|
+
# Verify results contain "python"
|
116
|
+
for row in rows:
|
117
|
+
title_content = f"{row[0]} {row[1]}".lower() # title and content
|
118
|
+
assert "python" in title_content
|
119
|
+
|
120
|
+
@pytest.mark.asyncio
|
121
|
+
async def test_async_transaction_simple_query_with_score(self, async_client_setup):
|
122
|
+
"""Test async transaction simple_query with score"""
|
123
|
+
client, test_db = async_client_setup
|
124
|
+
|
125
|
+
async with client.transaction() as tx:
|
126
|
+
# Test search with score within transaction
|
127
|
+
result = await tx.query(
|
128
|
+
"async_tx_docs.title",
|
129
|
+
"async_tx_docs.content",
|
130
|
+
boolean_match("title", "content").encourage("async").label("score"),
|
131
|
+
).execute()
|
132
|
+
|
133
|
+
assert result is not None
|
134
|
+
rows = result.fetchall()
|
135
|
+
assert len(rows) > 0
|
136
|
+
|
137
|
+
# Verify results have score column
|
138
|
+
assert "score" in result.columns
|
139
|
+
score_column_index = result.columns.index("score")
|
140
|
+
|
141
|
+
# Verify score values are numeric
|
142
|
+
for row in rows:
|
143
|
+
score = row[score_column_index]
|
144
|
+
assert isinstance(score, (int, float))
|
145
|
+
assert score >= 0
|
146
|
+
|
147
|
+
@pytest.mark.asyncio
|
148
|
+
async def test_async_transaction_simple_query_boolean_mode(self, async_client_setup):
|
149
|
+
"""Test async transaction simple_query with boolean mode"""
|
150
|
+
client, test_db = async_client_setup
|
151
|
+
|
152
|
+
async with client.transaction() as tx:
|
153
|
+
# Test boolean mode search within transaction
|
154
|
+
result = (
|
155
|
+
await tx.query("async_tx_docs.title", "async_tx_docs.content")
|
156
|
+
.filter(boolean_match("title", "content").must("async").must_not("basic"))
|
157
|
+
.execute()
|
158
|
+
)
|
159
|
+
|
160
|
+
assert result is not None
|
161
|
+
rows = result.fetchall()
|
162
|
+
assert len(rows) > 0
|
163
|
+
|
164
|
+
# Verify all results contain "async" but not "basic"
|
165
|
+
for row in rows:
|
166
|
+
title_content = f"{row[0]} {row[1]}".lower() # title and content
|
167
|
+
assert "async" in title_content
|
168
|
+
assert "basic" not in title_content
|
169
|
+
|
170
|
+
@pytest.mark.asyncio
|
171
|
+
async def test_async_transaction_simple_query_with_where(self, async_client_setup):
|
172
|
+
"""Test async transaction simple_query with WHERE conditions"""
|
173
|
+
client, test_db = async_client_setup
|
174
|
+
|
175
|
+
async with client.transaction() as tx:
|
176
|
+
# Test search with WHERE conditions within transaction
|
177
|
+
result = (
|
178
|
+
await tx.query("async_tx_docs.title", "async_tx_docs.content")
|
179
|
+
.filter(boolean_match("title", "content").encourage("programming"), "async_tx_docs.category = 'Programming'")
|
180
|
+
.execute()
|
181
|
+
)
|
182
|
+
|
183
|
+
assert result is not None
|
184
|
+
rows = result.fetchall()
|
185
|
+
assert len(rows) > 0
|
186
|
+
|
187
|
+
# Verify all results contain "programming"
|
188
|
+
for row in rows:
|
189
|
+
title_content = f"{row[0]} {row[1]}".lower()
|
190
|
+
assert "programming" in title_content
|
191
|
+
|
192
|
+
@pytest.mark.asyncio
|
193
|
+
async def test_async_transaction_simple_query_ordering_and_limit(self, async_client_setup):
|
194
|
+
"""Test async transaction simple_query with ordering and limit"""
|
195
|
+
client, test_db = async_client_setup
|
196
|
+
|
197
|
+
async with client.transaction() as tx:
|
198
|
+
# Test search with ordering and limit within transaction
|
199
|
+
result = (
|
200
|
+
await tx.query("async_tx_docs.title", "async_tx_docs.content")
|
201
|
+
.filter(boolean_match("title", "content").encourage("async"))
|
202
|
+
.order_by("async_tx_docs.title DESC")
|
203
|
+
.limit(2)
|
204
|
+
.execute()
|
205
|
+
)
|
206
|
+
|
207
|
+
assert result is not None
|
208
|
+
rows = result.fetchall()
|
209
|
+
assert len(rows) <= 2
|
210
|
+
|
211
|
+
# Verify results are ordered by title (descending)
|
212
|
+
titles = [row[0] for row in rows] # title column
|
213
|
+
assert titles == sorted(titles, reverse=True)
|
214
|
+
|
215
|
+
@pytest.mark.asyncio
|
216
|
+
async def test_async_transaction_simple_query_explain(self, async_client_setup):
|
217
|
+
"""Test async transaction simple_query explain functionality"""
|
218
|
+
client, test_db = async_client_setup
|
219
|
+
|
220
|
+
async with client.transaction() as tx:
|
221
|
+
# Test basic query functionality within transaction
|
222
|
+
result = (
|
223
|
+
await tx.query("async_tx_docs.title", "async_tx_docs.content")
|
224
|
+
.filter(boolean_match("title", "content").encourage("test"), "async_tx_docs.category = 'Testing'")
|
225
|
+
.execute()
|
226
|
+
)
|
227
|
+
|
228
|
+
assert result is not None
|
229
|
+
rows = result.fetchall()
|
230
|
+
# Should find testing-related content
|
231
|
+
assert len(rows) > 0, "Should find testing-related content"
|
232
|
+
|
233
|
+
# Verify all results contain "test" in title or content
|
234
|
+
for row in rows:
|
235
|
+
title_content = f"{row[0]} {row[1]}".lower()
|
236
|
+
assert "test" in title_content, f"Row should contain 'test': {row}"
|
237
|
+
|
238
|
+
@pytest.mark.asyncio
|
239
|
+
async def test_async_transaction_simple_query_multiple_operations(self, async_client_setup):
|
240
|
+
"""Test multiple async transaction simple_query operations in one transaction"""
|
241
|
+
client, test_db = async_client_setup
|
242
|
+
|
243
|
+
async with client.transaction() as tx:
|
244
|
+
# Test multiple searches within the same transaction
|
245
|
+
results = []
|
246
|
+
|
247
|
+
# Search 1: Basic search
|
248
|
+
result1 = await tx.query(
|
249
|
+
"async_tx_docs.title", "async_tx_docs.content", boolean_match("title", "content").encourage("python")
|
250
|
+
).execute()
|
251
|
+
results.append(result1)
|
252
|
+
|
253
|
+
# Search 2: Boolean mode
|
254
|
+
result2 = await tx.query(
|
255
|
+
"async_tx_docs.title", "async_tx_docs.content", boolean_match("title", "content").must("javascript")
|
256
|
+
).execute()
|
257
|
+
results.append(result2)
|
258
|
+
|
259
|
+
# Search 3: With score
|
260
|
+
result3 = await tx.query(
|
261
|
+
"async_tx_docs.title",
|
262
|
+
"async_tx_docs.content",
|
263
|
+
boolean_match("title", "content").encourage("database").label("score"),
|
264
|
+
).execute()
|
265
|
+
results.append(result3)
|
266
|
+
|
267
|
+
# Verify all searches returned results
|
268
|
+
for i, result in enumerate(results):
|
269
|
+
assert result is not None
|
270
|
+
rows = result.fetchall()
|
271
|
+
assert len(rows) > 0, f"Search {i+1} should return results"
|
272
|
+
|
273
|
+
# Verify content matches search terms
|
274
|
+
search_terms = ["python", "javascript", "database"]
|
275
|
+
term = search_terms[i]
|
276
|
+
for row in rows:
|
277
|
+
title_content = f"{row[0]} {row[1]}".lower()
|
278
|
+
assert term in title_content, f"Row should contain '{term}': {row}"
|
279
|
+
|
280
|
+
@pytest.mark.asyncio
|
281
|
+
async def test_async_transaction_simple_query_with_database_prefix(self, async_client_setup):
|
282
|
+
"""Test async transaction simple_query with database prefix"""
|
283
|
+
client, test_db = async_client_setup
|
284
|
+
|
285
|
+
async with client.transaction() as tx:
|
286
|
+
# Test search with database prefix within transaction
|
287
|
+
result = await tx.query(
|
288
|
+
f"{test_db}.async_tx_docs.title",
|
289
|
+
f"{test_db}.async_tx_docs.content",
|
290
|
+
boolean_match("title", "content").encourage("web"),
|
291
|
+
).execute()
|
292
|
+
|
293
|
+
assert result is not None
|
294
|
+
rows = result.fetchall()
|
295
|
+
assert len(rows) > 0
|
296
|
+
|
297
|
+
# Verify results contain "web"
|
298
|
+
for row in rows:
|
299
|
+
title_content = f"{row[0]} {row[1]}".lower() # title and content
|
300
|
+
assert "web" in title_content
|
@@ -0,0 +1,130 @@
|
|
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
|
+
Online tests for basic connection functionality
|
17
|
+
|
18
|
+
These tests are inspired by example_01_basic_connection.py
|
19
|
+
"""
|
20
|
+
|
21
|
+
import pytest
|
22
|
+
from matrixone import Client, AsyncClient
|
23
|
+
from matrixone.logger import create_default_logger
|
24
|
+
|
25
|
+
|
26
|
+
@pytest.mark.online
|
27
|
+
class TestBasicConnection:
|
28
|
+
"""Test basic connection functionality"""
|
29
|
+
|
30
|
+
def test_basic_connection_and_query(self, test_client):
|
31
|
+
"""Test basic connection and simple query"""
|
32
|
+
# Test basic query
|
33
|
+
result = test_client.execute("SELECT 1 as test_value, USER() as user_info")
|
34
|
+
assert result is not None
|
35
|
+
assert len(result.rows) > 0
|
36
|
+
assert result.rows[0][0] == 1 # test_value should be 1
|
37
|
+
|
38
|
+
def test_database_info_queries(self, test_client):
|
39
|
+
"""Test database information queries"""
|
40
|
+
# Test SHOW DATABASES
|
41
|
+
result = test_client.execute("SHOW DATABASES")
|
42
|
+
assert result is not None
|
43
|
+
assert len(result.rows) > 0
|
44
|
+
|
45
|
+
# Test SHOW TABLES
|
46
|
+
result = test_client.execute("SHOW TABLES")
|
47
|
+
assert result is not None
|
48
|
+
|
49
|
+
def test_connection_with_logging(self, connection_params):
|
50
|
+
"""Test connection with custom logging"""
|
51
|
+
host, port, user, password, database = connection_params
|
52
|
+
|
53
|
+
# Create logger
|
54
|
+
logger = create_default_logger()
|
55
|
+
|
56
|
+
# Create client with logging
|
57
|
+
client = Client()
|
58
|
+
client.connect(host=host, port=port, user=user, password=password, database=database)
|
59
|
+
|
60
|
+
try:
|
61
|
+
# Test query with logging
|
62
|
+
result = client.execute("SELECT 1 as test_value")
|
63
|
+
assert result is not None
|
64
|
+
assert len(result.rows) > 0
|
65
|
+
finally:
|
66
|
+
client.disconnect()
|
67
|
+
|
68
|
+
def test_connection_error_handling(self, connection_params):
|
69
|
+
"""Test connection error handling"""
|
70
|
+
host, port, user, password, database = connection_params
|
71
|
+
|
72
|
+
# Test with invalid port
|
73
|
+
try:
|
74
|
+
client = Client()
|
75
|
+
client.connect(host=host, port=9999, user=user, password=password, database=database)
|
76
|
+
# With the new connection pool architecture, connection is tested on first query
|
77
|
+
client.execute("SELECT 1")
|
78
|
+
assert False, "Should have failed with invalid port"
|
79
|
+
except Exception as e:
|
80
|
+
assert "connection" in str(e).lower() or "refused" in str(e).lower()
|
81
|
+
|
82
|
+
def test_client_context_manager(self, connection_params):
|
83
|
+
"""Test client context manager"""
|
84
|
+
host, port, user, password, database = connection_params
|
85
|
+
|
86
|
+
with Client() as client:
|
87
|
+
client.connect(host=host, port=port, user=user, password=password, database=database)
|
88
|
+
result = client.execute("SELECT 1 as test_value")
|
89
|
+
assert result is not None
|
90
|
+
assert len(result.rows) > 0
|
91
|
+
|
92
|
+
@pytest.mark.asyncio
|
93
|
+
async def test_async_basic_connection(self, test_async_client):
|
94
|
+
"""Test basic async connection and query"""
|
95
|
+
# Test basic query
|
96
|
+
result = await test_async_client.execute("SELECT 1 as test_value, USER() as user_info")
|
97
|
+
assert result is not None
|
98
|
+
assert len(result.rows) > 0
|
99
|
+
assert result.rows[0][0] == 1 # test_value should be 1
|
100
|
+
|
101
|
+
@pytest.mark.asyncio
|
102
|
+
async def test_async_connection_with_logging(self, connection_params):
|
103
|
+
"""Test async connection with custom logging"""
|
104
|
+
host, port, user, password, database = connection_params
|
105
|
+
|
106
|
+
# Create logger
|
107
|
+
logger = create_default_logger()
|
108
|
+
|
109
|
+
# Create async client with logging
|
110
|
+
client = AsyncClient()
|
111
|
+
await client.connect(host=host, port=port, user=user, password=password, database=database)
|
112
|
+
|
113
|
+
try:
|
114
|
+
# Test query with logging
|
115
|
+
result = await client.execute("SELECT 1 as test_value")
|
116
|
+
assert result is not None
|
117
|
+
assert len(result.rows) > 0
|
118
|
+
finally:
|
119
|
+
await client.disconnect()
|
120
|
+
|
121
|
+
@pytest.mark.asyncio
|
122
|
+
async def test_async_client_context_manager(self, connection_params):
|
123
|
+
"""Test async client context manager"""
|
124
|
+
host, port, user, password, database = connection_params
|
125
|
+
|
126
|
+
async with AsyncClient() as client:
|
127
|
+
await client.connect(host=host, port=port, user=user, password=password, database=database)
|
128
|
+
result = await client.execute("SELECT 1 as test_value")
|
129
|
+
assert result is not None
|
130
|
+
assert len(result.rows) > 0
|
@@ -0,0 +1,238 @@
|
|
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
|
+
Online tests for Client functionality - tests actual database operations
|
17
|
+
"""
|
18
|
+
|
19
|
+
import unittest
|
20
|
+
import os
|
21
|
+
import sys
|
22
|
+
from datetime import datetime
|
23
|
+
|
24
|
+
# Add the matrixone package to the path
|
25
|
+
sys.path.insert(0, os.path.join(os.path.dirname(__file__), '..', '..'))
|
26
|
+
|
27
|
+
from matrixone import Client
|
28
|
+
from matrixone.exceptions import ConnectionError, QueryError
|
29
|
+
|
30
|
+
|
31
|
+
class TestClientOnline(unittest.TestCase):
|
32
|
+
"""Online tests for Client functionality"""
|
33
|
+
|
34
|
+
@classmethod
|
35
|
+
def setUpClass(cls):
|
36
|
+
"""Set up test database connection"""
|
37
|
+
cls.client = Client(
|
38
|
+
host=os.getenv('MATRIXONE_HOST', '127.0.0.1'),
|
39
|
+
port=int(os.getenv('MATRIXONE_PORT', '6001')),
|
40
|
+
user=os.getenv('MATRIXONE_USER', 'root'),
|
41
|
+
password=os.getenv('MATRIXONE_PASSWORD', '111'),
|
42
|
+
database=os.getenv('MATRIXONE_DATABASE', 'test'),
|
43
|
+
)
|
44
|
+
|
45
|
+
# Create test database and table
|
46
|
+
cls.test_db = "test_client_db"
|
47
|
+
cls.test_table = "test_client_table"
|
48
|
+
|
49
|
+
try:
|
50
|
+
cls.client.execute(f"CREATE DATABASE IF NOT EXISTS {cls.test_db}")
|
51
|
+
cls.client.execute(f"USE {cls.test_db}")
|
52
|
+
cls.client.execute(
|
53
|
+
f"""
|
54
|
+
CREATE TABLE IF NOT EXISTS {cls.test_table} (
|
55
|
+
id INT PRIMARY KEY,
|
56
|
+
name VARCHAR(100),
|
57
|
+
value INT,
|
58
|
+
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
|
59
|
+
)
|
60
|
+
"""
|
61
|
+
)
|
62
|
+
|
63
|
+
# Insert test data
|
64
|
+
cls.client.execute(f"INSERT INTO {cls.test_table} VALUES (1, 'test1', 100, NOW())")
|
65
|
+
cls.client.execute(f"INSERT INTO {cls.test_table} VALUES (2, 'test2', 200, NOW())")
|
66
|
+
cls.client.execute(f"INSERT INTO {cls.test_table} VALUES (3, 'test3', 300, NOW())")
|
67
|
+
|
68
|
+
except Exception as e:
|
69
|
+
print(f"Setup failed: {e}")
|
70
|
+
raise
|
71
|
+
|
72
|
+
@classmethod
|
73
|
+
def tearDownClass(cls):
|
74
|
+
"""Clean up test database"""
|
75
|
+
try:
|
76
|
+
cls.client.execute(f"DROP DATABASE IF EXISTS {cls.test_db}")
|
77
|
+
except Exception as e:
|
78
|
+
print(f"Cleanup failed: {e}")
|
79
|
+
|
80
|
+
def test_basic_connection_and_query(self):
|
81
|
+
"""Test basic connection and query functionality"""
|
82
|
+
# Test simple query
|
83
|
+
result = self.client.execute("SELECT 1 as test_value")
|
84
|
+
rows = result.fetchall()
|
85
|
+
self.assertEqual(len(rows), 1)
|
86
|
+
self.assertEqual(rows[0][0], 1)
|
87
|
+
|
88
|
+
# Test query with parameters
|
89
|
+
result = self.client.execute("SELECT ? as param_value", (42,))
|
90
|
+
rows = result.fetchall()
|
91
|
+
self.assertEqual(len(rows), 1)
|
92
|
+
self.assertEqual(rows[0][0], 42)
|
93
|
+
|
94
|
+
def test_table_operations(self):
|
95
|
+
"""Test table operations"""
|
96
|
+
# Test SELECT
|
97
|
+
result = self.client.execute(f"SELECT * FROM {self.test_table} ORDER BY id")
|
98
|
+
rows = result.fetchall()
|
99
|
+
self.assertEqual(len(rows), 3)
|
100
|
+
self.assertEqual(rows[0][0], 1) # id
|
101
|
+
self.assertEqual(rows[0][1], 'test1') # name
|
102
|
+
|
103
|
+
# Test INSERT
|
104
|
+
self.client.execute(f"INSERT INTO {self.test_table} VALUES (4, 'test4', 400, NOW())")
|
105
|
+
|
106
|
+
# Verify INSERT
|
107
|
+
result = self.client.execute(f"SELECT COUNT(*) FROM {self.test_table}")
|
108
|
+
count = result.fetchone()[0]
|
109
|
+
self.assertEqual(count, 4)
|
110
|
+
|
111
|
+
# Test UPDATE
|
112
|
+
self.client.execute(f"UPDATE {self.test_table} SET value = 500 WHERE id = 4")
|
113
|
+
|
114
|
+
# Verify UPDATE
|
115
|
+
result = self.client.execute(f"SELECT value FROM {self.test_table} WHERE id = 4")
|
116
|
+
value = result.fetchone()[0]
|
117
|
+
self.assertEqual(value, 500)
|
118
|
+
|
119
|
+
# Test DELETE
|
120
|
+
self.client.execute(f"DELETE FROM {self.test_table} WHERE id = 4")
|
121
|
+
|
122
|
+
# Verify DELETE
|
123
|
+
result = self.client.execute(f"SELECT COUNT(*) FROM {self.test_table}")
|
124
|
+
count = result.fetchone()[0]
|
125
|
+
self.assertEqual(count, 3)
|
126
|
+
|
127
|
+
def test_transaction_operations(self):
|
128
|
+
"""Test transaction operations"""
|
129
|
+
with self.client.transaction() as tx:
|
130
|
+
# Insert within transaction
|
131
|
+
tx.execute(f"INSERT INTO {self.test_table} VALUES (5, 'test5', 500, NOW())")
|
132
|
+
|
133
|
+
# Verify within transaction
|
134
|
+
result = tx.execute(f"SELECT COUNT(*) FROM {self.test_table}")
|
135
|
+
count = result.fetchone()[0]
|
136
|
+
self.assertEqual(count, 4)
|
137
|
+
|
138
|
+
# Verify transaction commit
|
139
|
+
result = self.client.execute(f"SELECT COUNT(*) FROM {self.test_table}")
|
140
|
+
count = result.fetchone()[0]
|
141
|
+
self.assertEqual(count, 4)
|
142
|
+
|
143
|
+
# Test transaction rollback
|
144
|
+
try:
|
145
|
+
with self.client.transaction() as tx:
|
146
|
+
tx.execute(f"INSERT INTO {self.test_table} VALUES (6, 'test6', 600, NOW())")
|
147
|
+
# Force rollback by raising exception
|
148
|
+
raise Exception("Test rollback")
|
149
|
+
except Exception:
|
150
|
+
pass
|
151
|
+
|
152
|
+
# Verify rollback
|
153
|
+
result = self.client.execute(f"SELECT COUNT(*) FROM {self.test_table}")
|
154
|
+
count = result.fetchone()[0]
|
155
|
+
self.assertEqual(count, 4) # Should still be 4, not 5
|
156
|
+
|
157
|
+
# Clean up
|
158
|
+
self.client.execute(f"DELETE FROM {self.test_table} WHERE id = 5")
|
159
|
+
|
160
|
+
def test_error_handling(self):
|
161
|
+
"""Test error handling"""
|
162
|
+
# Test connection error handling
|
163
|
+
with self.assertRaises(QueryError):
|
164
|
+
self.client.execute("SELECT * FROM non_existent_table")
|
165
|
+
|
166
|
+
# Test invalid SQL
|
167
|
+
with self.assertRaises(QueryError):
|
168
|
+
self.client.execute("INVALID SQL STATEMENT")
|
169
|
+
|
170
|
+
def test_result_set_operations(self):
|
171
|
+
"""Test result set operations"""
|
172
|
+
result = self.client.execute(f"SELECT * FROM {self.test_table} ORDER BY id")
|
173
|
+
|
174
|
+
# Test fetchone
|
175
|
+
first_row = result.fetchone()
|
176
|
+
self.assertIsNotNone(first_row)
|
177
|
+
self.assertEqual(first_row[0], 1)
|
178
|
+
|
179
|
+
# Test fetchmany
|
180
|
+
next_rows = result.fetchmany(2)
|
181
|
+
self.assertEqual(len(next_rows), 2)
|
182
|
+
self.assertEqual(next_rows[0][0], 2)
|
183
|
+
self.assertEqual(next_rows[1][0], 3)
|
184
|
+
|
185
|
+
# Test fetchall (should return remaining rows)
|
186
|
+
remaining_rows = result.fetchall()
|
187
|
+
self.assertEqual(len(remaining_rows), 0) # Should be empty
|
188
|
+
|
189
|
+
# Test column names
|
190
|
+
result = self.client.execute(f"SELECT id, name, value FROM {self.test_table} LIMIT 1")
|
191
|
+
columns = result.keys()
|
192
|
+
expected_columns = ['id', 'name', 'value']
|
193
|
+
self.assertEqual(list(columns), expected_columns)
|
194
|
+
|
195
|
+
def test_parameter_binding(self):
|
196
|
+
"""Test parameter binding"""
|
197
|
+
# Test string parameters
|
198
|
+
result = self.client.execute(f"SELECT * FROM {self.test_table} WHERE name = ?", ('test1',))
|
199
|
+
rows = result.fetchall()
|
200
|
+
self.assertEqual(len(rows), 1)
|
201
|
+
self.assertEqual(rows[0][1], 'test1')
|
202
|
+
|
203
|
+
# Test numeric parameters
|
204
|
+
result = self.client.execute(f"SELECT * FROM {self.test_table} WHERE value = ?", (200,))
|
205
|
+
rows = result.fetchall()
|
206
|
+
self.assertEqual(len(rows), 1)
|
207
|
+
self.assertEqual(rows[0][2], 200)
|
208
|
+
|
209
|
+
# Test multiple parameters
|
210
|
+
result = self.client.execute(f"SELECT * FROM {self.test_table} WHERE name = ? AND value > ?", ('test2', 150))
|
211
|
+
rows = result.fetchall()
|
212
|
+
self.assertEqual(len(rows), 1)
|
213
|
+
self.assertEqual(rows[0][1], 'test2')
|
214
|
+
self.assertEqual(rows[0][2], 200)
|
215
|
+
|
216
|
+
def test_connection_pooling(self):
|
217
|
+
"""Test connection pooling and reuse"""
|
218
|
+
# Execute multiple queries to test connection reuse
|
219
|
+
for i in range(5):
|
220
|
+
result = self.client.execute("SELECT 1 as test")
|
221
|
+
rows = result.fetchall()
|
222
|
+
self.assertEqual(len(rows), 1)
|
223
|
+
self.assertEqual(rows[0][0], 1)
|
224
|
+
|
225
|
+
# Test concurrent-like operations
|
226
|
+
results = []
|
227
|
+
for i in range(3):
|
228
|
+
result = self.client.execute(f"SELECT COUNT(*) FROM {self.test_table}")
|
229
|
+
count = result.fetchone()[0]
|
230
|
+
results.append(count)
|
231
|
+
|
232
|
+
# All should return the same count
|
233
|
+
for count in results:
|
234
|
+
self.assertEqual(count, 3)
|
235
|
+
|
236
|
+
|
237
|
+
if __name__ == '__main__':
|
238
|
+
unittest.main()
|