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,998 @@
|
|
1
|
+
#!/usr/bin/env python3
|
2
|
+
|
3
|
+
# Copyright 2021 - 2022 Matrix Origin
|
4
|
+
#
|
5
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
6
|
+
# you may not use this file except in compliance with the License.
|
7
|
+
# You may obtain a copy of the License at
|
8
|
+
#
|
9
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
10
|
+
#
|
11
|
+
# Unless required by applicable law or agreed to in writing, software
|
12
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
13
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
14
|
+
# See the License for the specific language governing permissions and
|
15
|
+
# limitations under the License.
|
16
|
+
|
17
|
+
"""
|
18
|
+
Comprehensive online tests for snapshot functionality.
|
19
|
+
Merged from multiple snapshot test files to reduce redundancy while maintaining coverage.
|
20
|
+
|
21
|
+
This file consolidates tests from:
|
22
|
+
- test_snapshot_online.py (10 tests)
|
23
|
+
- test_snapshot_restore.py (6 tests)
|
24
|
+
- test_client_online.py (2 snapshot tests)
|
25
|
+
- test_async_client_online.py (2 async snapshot tests)
|
26
|
+
- test_matrixone_query_orm.py (3 snapshot tests)
|
27
|
+
- test_async_client_interfaces.py (2 snapshot tests)
|
28
|
+
- test_async_orm_online.py (1 snapshot test)
|
29
|
+
|
30
|
+
Total: 26 snapshot tests consolidated into one comprehensive file
|
31
|
+
"""
|
32
|
+
|
33
|
+
import pytest
|
34
|
+
import pytest_asyncio
|
35
|
+
import unittest
|
36
|
+
import os
|
37
|
+
import sys
|
38
|
+
import time
|
39
|
+
from datetime import datetime
|
40
|
+
|
41
|
+
# Add the matrixone package to the path
|
42
|
+
sys.path.insert(0, os.path.join(os.path.dirname(__file__), '..', '..'))
|
43
|
+
|
44
|
+
from matrixone import Client, AsyncClient
|
45
|
+
from matrixone.snapshot import SnapshotLevel, SnapshotManager
|
46
|
+
from matrixone.exceptions import SnapshotError, ConnectionError
|
47
|
+
from matrixone.logger import create_default_logger
|
48
|
+
from .test_config import online_config
|
49
|
+
|
50
|
+
|
51
|
+
@pytest.mark.online
|
52
|
+
class TestSnapshotComprehensive:
|
53
|
+
"""Comprehensive online tests for snapshot functionality"""
|
54
|
+
|
55
|
+
@pytest.fixture(scope="class")
|
56
|
+
def test_client(self):
|
57
|
+
"""Create and connect Client for testing"""
|
58
|
+
host, port, user, password, database = online_config.get_connection_params()
|
59
|
+
client = Client()
|
60
|
+
client.connect(host=host, port=port, user=user, password=password, database=database)
|
61
|
+
try:
|
62
|
+
yield client
|
63
|
+
finally:
|
64
|
+
try:
|
65
|
+
client.disconnect()
|
66
|
+
except Exception as e:
|
67
|
+
print(f"Warning: Failed to disconnect client: {e}")
|
68
|
+
|
69
|
+
@pytest_asyncio.fixture(scope="function")
|
70
|
+
async def test_async_client(self):
|
71
|
+
"""Create and connect AsyncClient for testing"""
|
72
|
+
host, port, user, password, database = online_config.get_connection_params()
|
73
|
+
client = AsyncClient()
|
74
|
+
await client.connect(host=host, port=port, user=user, password=password, database=database)
|
75
|
+
try:
|
76
|
+
yield client
|
77
|
+
finally:
|
78
|
+
try:
|
79
|
+
await client.disconnect()
|
80
|
+
except Exception as e:
|
81
|
+
print(f"Warning: Failed to disconnect async client: {e}")
|
82
|
+
|
83
|
+
@pytest.fixture(scope="class")
|
84
|
+
def test_database(self, test_client):
|
85
|
+
"""Set up test database and table"""
|
86
|
+
test_db = "test_snapshot_db"
|
87
|
+
test_table = "test_snapshot_table"
|
88
|
+
|
89
|
+
try:
|
90
|
+
test_client.execute(f"CREATE DATABASE IF NOT EXISTS {test_db}")
|
91
|
+
test_client.execute(f"USE {test_db}")
|
92
|
+
test_client.execute(
|
93
|
+
f"""
|
94
|
+
CREATE TABLE IF NOT EXISTS {test_table} (
|
95
|
+
id INT PRIMARY KEY,
|
96
|
+
name VARCHAR(100),
|
97
|
+
value INT
|
98
|
+
)
|
99
|
+
"""
|
100
|
+
)
|
101
|
+
|
102
|
+
# Clear existing data and insert test data
|
103
|
+
test_client.execute(f"DELETE FROM {test_table}")
|
104
|
+
test_client.execute(f"INSERT INTO {test_table} VALUES (1, 'test1', 100)")
|
105
|
+
test_client.execute(f"INSERT INTO {test_table} VALUES (2, 'test2', 200)")
|
106
|
+
test_client.execute(f"INSERT INTO {test_table} VALUES (3, 'test3', 300)")
|
107
|
+
|
108
|
+
yield test_db, test_table
|
109
|
+
|
110
|
+
finally:
|
111
|
+
# Clean up
|
112
|
+
try:
|
113
|
+
test_client.execute(f"DROP DATABASE IF EXISTS {test_db}")
|
114
|
+
except Exception as e:
|
115
|
+
print(f"Cleanup failed: {e}")
|
116
|
+
|
117
|
+
@pytest_asyncio.fixture(scope="function")
|
118
|
+
async def async_test_database(self, test_async_client):
|
119
|
+
"""Set up test database and table for async tests"""
|
120
|
+
test_db = "test_async_snapshot_db"
|
121
|
+
test_table = "test_async_snapshot_table"
|
122
|
+
|
123
|
+
try:
|
124
|
+
await test_async_client.execute(f"CREATE DATABASE IF NOT EXISTS {test_db}")
|
125
|
+
await test_async_client.execute(f"USE {test_db}")
|
126
|
+
await test_async_client.execute(
|
127
|
+
f"""
|
128
|
+
CREATE TABLE IF NOT EXISTS {test_table} (
|
129
|
+
id INT PRIMARY KEY,
|
130
|
+
name VARCHAR(100),
|
131
|
+
value INT
|
132
|
+
)
|
133
|
+
"""
|
134
|
+
)
|
135
|
+
|
136
|
+
# Clear existing data and insert test data
|
137
|
+
await test_async_client.execute(f"DELETE FROM {test_table}")
|
138
|
+
await test_async_client.execute(f"INSERT INTO {test_table} VALUES (1, 'async_test1', 100)")
|
139
|
+
await test_async_client.execute(f"INSERT INTO {test_table} VALUES (2, 'async_test2', 200)")
|
140
|
+
await test_async_client.execute(f"INSERT INTO {test_table} VALUES (3, 'async_test3', 300)")
|
141
|
+
|
142
|
+
yield test_db, test_table
|
143
|
+
|
144
|
+
finally:
|
145
|
+
# Clean up
|
146
|
+
try:
|
147
|
+
await test_async_client.execute(f"DROP DATABASE IF EXISTS {test_db}")
|
148
|
+
except Exception as e:
|
149
|
+
print(f"Async cleanup failed: {e}")
|
150
|
+
|
151
|
+
# ==================== BASIC SNAPSHOT OPERATIONS ====================
|
152
|
+
|
153
|
+
def test_snapshot_creation_and_management(self, test_client, test_database):
|
154
|
+
"""Test creating and managing snapshots - from test_snapshot_online.py"""
|
155
|
+
snapshot_name = f"test_snap_{datetime.now().strftime('%Y%m%d_%H%M%S')}"
|
156
|
+
|
157
|
+
# Test cluster snapshot creation
|
158
|
+
snapshot = test_client.snapshots.create(snapshot_name, SnapshotLevel.CLUSTER)
|
159
|
+
assert snapshot.name == snapshot_name
|
160
|
+
assert snapshot.level == SnapshotLevel.CLUSTER
|
161
|
+
|
162
|
+
# Test snapshot exists
|
163
|
+
assert test_client.snapshots.exists(snapshot_name)
|
164
|
+
|
165
|
+
# Test getting snapshot
|
166
|
+
retrieved_snapshot = test_client.snapshots.get(snapshot_name)
|
167
|
+
assert retrieved_snapshot.name == snapshot_name
|
168
|
+
|
169
|
+
# Test listing snapshots
|
170
|
+
snapshots = test_client.snapshots.list()
|
171
|
+
snapshot_names = [s.name for s in snapshots]
|
172
|
+
assert snapshot_name in snapshot_names
|
173
|
+
|
174
|
+
# Test snapshot deletion
|
175
|
+
test_client.snapshots.delete(snapshot_name)
|
176
|
+
assert not test_client.snapshots.exists(snapshot_name)
|
177
|
+
|
178
|
+
def test_table_snapshot_creation(self, test_client, test_database):
|
179
|
+
"""Test creating table-level snapshots - from test_snapshot_online.py"""
|
180
|
+
snapshot_name = f"test_table_snap_{datetime.now().strftime('%Y%m%d_%H%M%S')}"
|
181
|
+
|
182
|
+
# Test table snapshot creation
|
183
|
+
snapshot = test_client.snapshots.create(
|
184
|
+
snapshot_name,
|
185
|
+
SnapshotLevel.TABLE,
|
186
|
+
database="test_snapshot_db",
|
187
|
+
table="test_snapshot_table",
|
188
|
+
)
|
189
|
+
assert snapshot.name == snapshot_name
|
190
|
+
assert snapshot.level == SnapshotLevel.TABLE
|
191
|
+
assert snapshot.database == "test_snapshot_db"
|
192
|
+
assert snapshot.table == "test_snapshot_table"
|
193
|
+
|
194
|
+
# Clean up
|
195
|
+
test_client.snapshots.delete(snapshot_name)
|
196
|
+
|
197
|
+
def test_basic_snapshot_operations(self, test_client):
|
198
|
+
"""Test basic snapshot creation and management - from test_snapshot_restore.py"""
|
199
|
+
# Create test data in the correct database
|
200
|
+
test_db = online_config.get_test_database()
|
201
|
+
test_client.execute(
|
202
|
+
f"CREATE TABLE IF NOT EXISTS {test_db}.snapshot_test (id INT PRIMARY KEY, name VARCHAR(50), value INT)"
|
203
|
+
)
|
204
|
+
test_client.execute(f"DELETE FROM {test_db}.snapshot_test")
|
205
|
+
test_client.execute(f"INSERT INTO {test_db}.snapshot_test VALUES (1, 'test1', 100)")
|
206
|
+
test_client.execute(f"INSERT INTO {test_db}.snapshot_test VALUES (2, 'test2', 200)")
|
207
|
+
|
208
|
+
# Verify initial data
|
209
|
+
result = test_client.execute(f"SELECT COUNT(*) FROM {test_db}.snapshot_test")
|
210
|
+
assert result.rows[0][0] == 2
|
211
|
+
|
212
|
+
# Create snapshot
|
213
|
+
snapshot_name = f"testsnapshot{int(time.time())}"
|
214
|
+
try:
|
215
|
+
snapshot = test_client.snapshots.create(
|
216
|
+
name=snapshot_name,
|
217
|
+
level="table",
|
218
|
+
database=test_db,
|
219
|
+
table="snapshot_test",
|
220
|
+
description="Test snapshot for basic operations",
|
221
|
+
)
|
222
|
+
assert snapshot is not None
|
223
|
+
assert snapshot.name == snapshot_name
|
224
|
+
except Exception as e:
|
225
|
+
pytest.skip(f"Snapshot creation not supported: {e}")
|
226
|
+
|
227
|
+
# Add more data after snapshot
|
228
|
+
test_client.execute(f"INSERT INTO {test_db}.snapshot_test VALUES (3, 'test3', 300)")
|
229
|
+
test_client.execute(f"INSERT INTO {test_db}.snapshot_test VALUES (4, 'test4', 400)")
|
230
|
+
|
231
|
+
result = test_client.execute(f"SELECT COUNT(*) FROM {test_db}.snapshot_test")
|
232
|
+
assert result.rows[0][0] == 4
|
233
|
+
|
234
|
+
# List snapshots
|
235
|
+
try:
|
236
|
+
snapshots = test_client.snapshots.list()
|
237
|
+
assert isinstance(snapshots, list)
|
238
|
+
# Check if our snapshot is in the list
|
239
|
+
snapshot_names = [s.name for s in snapshots]
|
240
|
+
assert snapshot_name in snapshot_names
|
241
|
+
except Exception as e:
|
242
|
+
pytest.skip(f"Snapshot listing not supported: {e}")
|
243
|
+
|
244
|
+
# Cleanup
|
245
|
+
try:
|
246
|
+
test_client.snapshots.delete(snapshot_name)
|
247
|
+
except Exception:
|
248
|
+
pass # Ignore cleanup errors
|
249
|
+
|
250
|
+
test_client.execute(f"DROP TABLE IF EXISTS {test_db}.snapshot_test")
|
251
|
+
|
252
|
+
def test_snapshot_enumeration(self, test_client):
|
253
|
+
"""Test snapshot enumeration and information - from test_snapshot_restore.py"""
|
254
|
+
# Create test table in the correct database
|
255
|
+
test_db = online_config.get_test_database()
|
256
|
+
test_client.execute(
|
257
|
+
f"CREATE TABLE IF NOT EXISTS {test_db}.snapshot_test (id INT PRIMARY KEY, name VARCHAR(50), value INT)"
|
258
|
+
)
|
259
|
+
test_client.execute(f"DELETE FROM {test_db}.snapshot_test")
|
260
|
+
test_client.execute(f"INSERT INTO {test_db}.snapshot_test VALUES (1, 'enum_test1', 100)")
|
261
|
+
test_client.execute(f"INSERT INTO {test_db}.snapshot_test VALUES (2, 'enum_test2', 200)")
|
262
|
+
|
263
|
+
# Create multiple snapshots
|
264
|
+
snapshot_names = []
|
265
|
+
for i in range(3):
|
266
|
+
snapshot_name = f"enumsnapshot{i}{int(time.time())}"
|
267
|
+
try:
|
268
|
+
snapshot = test_client.snapshots.create(
|
269
|
+
name=snapshot_name,
|
270
|
+
level="table",
|
271
|
+
database=test_db,
|
272
|
+
table="snapshot_test",
|
273
|
+
description=f"Test snapshot {i}",
|
274
|
+
)
|
275
|
+
snapshot_names.append(snapshot_name)
|
276
|
+
except Exception as e:
|
277
|
+
pytest.skip(f"Snapshot creation not supported: {e}")
|
278
|
+
|
279
|
+
# List all snapshots
|
280
|
+
try:
|
281
|
+
snapshots = test_client.snapshots.list()
|
282
|
+
assert isinstance(snapshots, list)
|
283
|
+
assert len(snapshots) >= len(snapshot_names)
|
284
|
+
|
285
|
+
# Verify our snapshots are in the list
|
286
|
+
snapshot_names_in_list = [s.name for s in snapshots]
|
287
|
+
for name in snapshot_names:
|
288
|
+
assert name in snapshot_names_in_list
|
289
|
+
except Exception as e:
|
290
|
+
pytest.skip(f"Snapshot listing not supported: {e}")
|
291
|
+
|
292
|
+
# Cleanup
|
293
|
+
for snapshot_name in snapshot_names:
|
294
|
+
try:
|
295
|
+
test_client.snapshots.delete(snapshot_name)
|
296
|
+
except Exception:
|
297
|
+
pass # Ignore cleanup errors
|
298
|
+
|
299
|
+
test_client.execute(f"DROP TABLE IF EXISTS {test_db}.snapshot_test")
|
300
|
+
|
301
|
+
# ==================== SNAPSHOT QUERY OPERATIONS ====================
|
302
|
+
|
303
|
+
def test_snapshot_query_basic(self, test_client, test_database):
|
304
|
+
"""Test basic snapshot query builder functionality - from test_snapshot_online.py"""
|
305
|
+
snapshot_name = f"test_query_snap_{datetime.now().strftime('%Y%m%d_%H%M%S')}"
|
306
|
+
|
307
|
+
# Create snapshot first
|
308
|
+
test_client.snapshots.create(
|
309
|
+
snapshot_name,
|
310
|
+
SnapshotLevel.TABLE,
|
311
|
+
database="test_snapshot_db",
|
312
|
+
table="test_snapshot_table",
|
313
|
+
)
|
314
|
+
|
315
|
+
try:
|
316
|
+
# Test basic query
|
317
|
+
result = (
|
318
|
+
test_client.query("test_snapshot_db.test_snapshot_table", snapshot=snapshot_name)
|
319
|
+
.select("id", "name", "value")
|
320
|
+
.execute()
|
321
|
+
)
|
322
|
+
|
323
|
+
rows = result.fetchall()
|
324
|
+
assert len(rows) == 3
|
325
|
+
|
326
|
+
# Test query with WHERE condition
|
327
|
+
result = (
|
328
|
+
test_client.query("test_snapshot_db.test_snapshot_table", snapshot=snapshot_name)
|
329
|
+
.select("id", "name", "value")
|
330
|
+
.where("value > ?", 150)
|
331
|
+
.execute()
|
332
|
+
)
|
333
|
+
|
334
|
+
rows = result.fetchall()
|
335
|
+
assert len(rows) == 2 # Only rows with value > 150
|
336
|
+
|
337
|
+
finally:
|
338
|
+
# Clean up
|
339
|
+
test_client.snapshots.delete(snapshot_name)
|
340
|
+
|
341
|
+
def test_snapshot_query_parameter_substitution(self, test_client, test_database):
|
342
|
+
"""Test parameter substitution in snapshot queries - from test_snapshot_online.py"""
|
343
|
+
snapshot_name = f"test_param_snap_{datetime.now().strftime('%Y%m%d_%H%M%S')}"
|
344
|
+
|
345
|
+
# Create cluster snapshot first (more reliable than table snapshot)
|
346
|
+
test_client.snapshots.create(snapshot_name, SnapshotLevel.CLUSTER)
|
347
|
+
|
348
|
+
try:
|
349
|
+
# Test string parameter substitution
|
350
|
+
result = (
|
351
|
+
test_client.query("test_snapshot_db.test_snapshot_table", snapshot=snapshot_name)
|
352
|
+
.select("id", "name", "value")
|
353
|
+
.where("name = ?", "test1")
|
354
|
+
.execute()
|
355
|
+
)
|
356
|
+
|
357
|
+
rows = result.fetchall()
|
358
|
+
assert len(rows) == 1
|
359
|
+
assert rows[0][1] == "test1"
|
360
|
+
|
361
|
+
# Test numeric parameter substitution
|
362
|
+
result = (
|
363
|
+
test_client.query("test_snapshot_db.test_snapshot_table", snapshot=snapshot_name)
|
364
|
+
.select("id", "name", "value")
|
365
|
+
.where("value = ?", 200)
|
366
|
+
.execute()
|
367
|
+
)
|
368
|
+
|
369
|
+
rows = result.fetchall()
|
370
|
+
assert len(rows) == 1
|
371
|
+
assert rows[0][2] == 200
|
372
|
+
|
373
|
+
# Test multiple parameters
|
374
|
+
result = (
|
375
|
+
test_client.query("test_snapshot_db.test_snapshot_table", snapshot=snapshot_name)
|
376
|
+
.select("id", "name", "value")
|
377
|
+
.where("name = ? AND value > ?", "test2", 150)
|
378
|
+
.execute()
|
379
|
+
)
|
380
|
+
|
381
|
+
rows = result.fetchall()
|
382
|
+
assert len(rows) == 1
|
383
|
+
assert rows[0][1] == "test2"
|
384
|
+
|
385
|
+
finally:
|
386
|
+
# Clean up
|
387
|
+
test_client.snapshots.delete(snapshot_name)
|
388
|
+
|
389
|
+
def test_snapshot_query_complex_query(self, test_client, test_database):
|
390
|
+
"""Test complex snapshot query with all clauses - from test_snapshot_online.py"""
|
391
|
+
snapshot_name = f"test_complex_snap_{datetime.now().strftime('%Y%m%d_%H%M%S')}"
|
392
|
+
|
393
|
+
# Create snapshot first
|
394
|
+
test_client.snapshots.create(
|
395
|
+
snapshot_name,
|
396
|
+
SnapshotLevel.TABLE,
|
397
|
+
database="test_snapshot_db",
|
398
|
+
table="test_snapshot_table",
|
399
|
+
)
|
400
|
+
|
401
|
+
try:
|
402
|
+
# Test complex query with ORDER BY and LIMIT
|
403
|
+
result = (
|
404
|
+
test_client.query("test_snapshot_db.test_snapshot_table", snapshot=snapshot_name)
|
405
|
+
.select("id", "name", "value")
|
406
|
+
.where("value > ?", 100)
|
407
|
+
.order_by("value DESC")
|
408
|
+
.limit(2)
|
409
|
+
.execute()
|
410
|
+
)
|
411
|
+
|
412
|
+
rows = result.fetchall()
|
413
|
+
assert len(rows) == 2
|
414
|
+
# Should be ordered by value DESC
|
415
|
+
assert rows[0][2] >= rows[1][2]
|
416
|
+
|
417
|
+
finally:
|
418
|
+
# Clean up
|
419
|
+
test_client.snapshots.delete(snapshot_name)
|
420
|
+
|
421
|
+
def test_snapshot_query_functionality(self, test_client, test_database):
|
422
|
+
"""Test snapshot query functionality - from test_client_online.py"""
|
423
|
+
test_db, test_table = test_database
|
424
|
+
snapshot_name = f"test_client_snap_{datetime.now().strftime('%Y%m%d_%H%M%S')}"
|
425
|
+
|
426
|
+
try:
|
427
|
+
# Create snapshot
|
428
|
+
test_client.snapshots.create(snapshot_name, "table", database=test_db, table=test_table)
|
429
|
+
|
430
|
+
# Test snapshot query using query builder
|
431
|
+
result = (
|
432
|
+
test_client.query(f"{test_db}.{test_table}", snapshot=snapshot_name)
|
433
|
+
.select("*")
|
434
|
+
.where("value > ?", 150)
|
435
|
+
.execute()
|
436
|
+
)
|
437
|
+
|
438
|
+
rows = result.fetchall()
|
439
|
+
assert len(rows) == 2 # Should have 2 rows with value > 150
|
440
|
+
|
441
|
+
# Test snapshot query without parameters
|
442
|
+
result = test_client.query(f"{test_db}.{test_table}", snapshot=snapshot_name).select("COUNT(*)").execute()
|
443
|
+
|
444
|
+
count = result.fetchone()[0]
|
445
|
+
assert count == 3
|
446
|
+
|
447
|
+
finally:
|
448
|
+
# Clean up snapshot
|
449
|
+
try:
|
450
|
+
test_client.snapshots.delete(snapshot_name)
|
451
|
+
except Exception:
|
452
|
+
pass
|
453
|
+
|
454
|
+
def test_snapshot_query_syntax_validation(self, test_client, test_database):
|
455
|
+
"""Test that snapshot queries use correct syntax - from test_snapshot_online.py and test_client_online.py"""
|
456
|
+
test_db, test_table = test_database
|
457
|
+
snapshot_name = f"test_syntax_snap_{datetime.now().strftime('%Y%m%d_%H%M%S')}"
|
458
|
+
|
459
|
+
try:
|
460
|
+
# Create snapshot
|
461
|
+
test_client.snapshots.create(snapshot_name, "table", database=test_db, table=test_table)
|
462
|
+
|
463
|
+
# This should work without errors - the SQL should be properly formatted
|
464
|
+
result = (
|
465
|
+
test_client.query(f"{test_db}.{test_table}", snapshot=snapshot_name)
|
466
|
+
.select("id", "name")
|
467
|
+
.where("id = ?", 1)
|
468
|
+
.execute()
|
469
|
+
)
|
470
|
+
|
471
|
+
rows = result.fetchall()
|
472
|
+
assert len(rows) == 1
|
473
|
+
|
474
|
+
finally:
|
475
|
+
# Clean up
|
476
|
+
test_client.snapshots.delete(snapshot_name)
|
477
|
+
|
478
|
+
# ==================== SNAPSHOT MANAGEMENT ====================
|
479
|
+
|
480
|
+
def test_snapshot_management_comprehensive(self, test_client, test_database):
|
481
|
+
"""Test comprehensive snapshot management functionality - from test_snapshot_online.py"""
|
482
|
+
snapshot_name = f"test_snapshot_001_{datetime.now().strftime('%Y%m%d_%H%M%S')}"
|
483
|
+
|
484
|
+
try:
|
485
|
+
# Create snapshot using the snapshot manager
|
486
|
+
snapshot = test_client.snapshots.create(
|
487
|
+
name=snapshot_name,
|
488
|
+
level="table",
|
489
|
+
database="test_snapshot_db",
|
490
|
+
table="test_snapshot_table",
|
491
|
+
description="Test snapshot for SDK",
|
492
|
+
)
|
493
|
+
|
494
|
+
assert snapshot is not None
|
495
|
+
assert snapshot.name == snapshot_name
|
496
|
+
|
497
|
+
# List snapshots
|
498
|
+
snapshots = test_client.snapshots.list()
|
499
|
+
snapshot_names = [s.name for s in snapshots]
|
500
|
+
assert snapshot_name in snapshot_names
|
501
|
+
|
502
|
+
# Get specific snapshot
|
503
|
+
retrieved_snapshot = test_client.snapshots.get(snapshot_name)
|
504
|
+
assert retrieved_snapshot.name == snapshot_name
|
505
|
+
|
506
|
+
# Test snapshot query using query builder
|
507
|
+
result = test_client.query("test_snapshot_db.test_snapshot_table", snapshot=snapshot_name).select("*").execute()
|
508
|
+
rows = result.fetchall()
|
509
|
+
assert len(rows) == 3 # We inserted 3 test rows
|
510
|
+
|
511
|
+
# Test snapshot context manager
|
512
|
+
with test_client.snapshot(snapshot_name) as snapshot_client:
|
513
|
+
result = snapshot_client.execute("SELECT COUNT(*) FROM test_snapshot_db.test_snapshot_table")
|
514
|
+
count = result.scalar()
|
515
|
+
assert count == 3
|
516
|
+
|
517
|
+
# Test snapshot query builder with WHERE clause
|
518
|
+
result = (
|
519
|
+
test_client.query("test_snapshot_db.test_snapshot_table", snapshot=snapshot_name)
|
520
|
+
.select("id", "name", "value")
|
521
|
+
.where("value > ?", 150)
|
522
|
+
.execute()
|
523
|
+
)
|
524
|
+
rows = result.fetchall()
|
525
|
+
assert len(rows) == 2 # Only rows with value > 150
|
526
|
+
|
527
|
+
finally:
|
528
|
+
# Clean up
|
529
|
+
test_client.snapshots.delete(snapshot_name)
|
530
|
+
|
531
|
+
# ==================== SNAPSHOT RESTORE OPERATIONS ====================
|
532
|
+
|
533
|
+
def test_snapshot_restore_operations(self, test_client):
|
534
|
+
"""Test snapshot restore operations - from test_snapshot_restore.py"""
|
535
|
+
# Create test data in the correct database
|
536
|
+
test_db = online_config.get_test_database()
|
537
|
+
test_client.execute(
|
538
|
+
f"CREATE TABLE IF NOT EXISTS {test_db}.snapshot_test (id INT PRIMARY KEY, name VARCHAR(50), value INT)"
|
539
|
+
)
|
540
|
+
test_client.execute(f"DELETE FROM {test_db}.snapshot_test")
|
541
|
+
test_client.execute(f"INSERT INTO {test_db}.snapshot_test VALUES (1, 'restore_test1', 100)")
|
542
|
+
test_client.execute(f"INSERT INTO {test_db}.snapshot_test VALUES (2, 'restore_test2', 200)")
|
543
|
+
|
544
|
+
# Create snapshot
|
545
|
+
snapshot_name = f"restoresnapshot{int(time.time())}"
|
546
|
+
try:
|
547
|
+
snapshot = test_client.snapshots.create(
|
548
|
+
name=snapshot_name,
|
549
|
+
level="table",
|
550
|
+
database=test_db,
|
551
|
+
table="snapshot_test",
|
552
|
+
description="Test snapshot for restore operations",
|
553
|
+
)
|
554
|
+
except Exception as e:
|
555
|
+
pytest.skip(f"Snapshot creation not supported: {e}")
|
556
|
+
|
557
|
+
# Add more data after snapshot
|
558
|
+
test_client.execute(f"INSERT INTO {test_db}.snapshot_test VALUES (3, 'restore_test3', 300)")
|
559
|
+
test_client.execute(f"INSERT INTO {test_db}.snapshot_test VALUES (4, 'restore_test4', 400)")
|
560
|
+
|
561
|
+
result = test_client.execute(f"SELECT COUNT(*) FROM {test_db}.snapshot_test")
|
562
|
+
assert result.rows[0][0] == 4
|
563
|
+
|
564
|
+
# Restore from snapshot
|
565
|
+
try:
|
566
|
+
test_client.restore.restore_table(snapshot_name, "sys", test_db, "snapshot_test")
|
567
|
+
|
568
|
+
# Verify data after restore
|
569
|
+
result = test_client.execute(f"SELECT COUNT(*) FROM {test_db}.snapshot_test")
|
570
|
+
assert result.rows[0][0] == 2 # Should be back to original 2 records
|
571
|
+
|
572
|
+
# Verify specific data
|
573
|
+
result = test_client.execute(f"SELECT * FROM {test_db}.snapshot_test ORDER BY id")
|
574
|
+
assert len(result.rows) == 2
|
575
|
+
assert result.rows[0][0] == 1 # ID: 1
|
576
|
+
assert result.rows[1][0] == 2 # ID: 2
|
577
|
+
except Exception as e:
|
578
|
+
pytest.skip(f"Snapshot restore not supported: {e}")
|
579
|
+
|
580
|
+
# Cleanup
|
581
|
+
try:
|
582
|
+
test_client.snapshots.delete(snapshot_name)
|
583
|
+
except Exception:
|
584
|
+
pass # Ignore cleanup errors
|
585
|
+
|
586
|
+
test_client.execute(f"DROP TABLE IF EXISTS {test_db}.snapshot_test")
|
587
|
+
|
588
|
+
# ==================== ERROR HANDLING ====================
|
589
|
+
|
590
|
+
def test_snapshot_error_handling(self, test_client, test_database):
|
591
|
+
"""Test snapshot error handling - from test_snapshot_online.py and test_snapshot_restore.py"""
|
592
|
+
# Test non-existent snapshot
|
593
|
+
with pytest.raises(SnapshotError):
|
594
|
+
test_client.snapshots.get("non_existent_snapshot")
|
595
|
+
|
596
|
+
# Test invalid snapshot level
|
597
|
+
with pytest.raises(SnapshotError):
|
598
|
+
test_client.snapshots.create("test", "invalid_level")
|
599
|
+
|
600
|
+
# Test creating snapshot with invalid parameters
|
601
|
+
try:
|
602
|
+
test_client.snapshots.create(
|
603
|
+
name="", # Empty name should fail
|
604
|
+
level="table",
|
605
|
+
database=online_config.get_test_database(),
|
606
|
+
table="nonexistent_table",
|
607
|
+
description="Test invalid snapshot",
|
608
|
+
)
|
609
|
+
assert False, "Should have failed with empty name"
|
610
|
+
except Exception:
|
611
|
+
pass # Expected to fail
|
612
|
+
|
613
|
+
# Test deleting non-existent snapshot
|
614
|
+
try:
|
615
|
+
test_client.snapshots.delete("nonexistent_snapshot")
|
616
|
+
assert False, "Should have failed with non-existent snapshot"
|
617
|
+
except Exception:
|
618
|
+
pass # Expected to fail
|
619
|
+
|
620
|
+
# Test restoring from non-existent snapshot
|
621
|
+
try:
|
622
|
+
test_client.restore.restore_table("nonexistent_snapshot", "sys", "test", "test_table")
|
623
|
+
assert False, "Should have failed with non-existent snapshot"
|
624
|
+
except Exception:
|
625
|
+
pass # Expected to fail
|
626
|
+
|
627
|
+
# ==================== SNAPSHOT SYNTAX VARIATIONS ====================
|
628
|
+
|
629
|
+
def test_snapshot_syntax_variations(self, test_client, test_database):
|
630
|
+
"""Test different snapshot syntax variations - from test_snapshot_online.py"""
|
631
|
+
# Create a test table for syntax testing
|
632
|
+
test_table = "test_syntax_table"
|
633
|
+
test_client.execute(f"CREATE TABLE IF NOT EXISTS {test_table} (id INT PRIMARY KEY, name VARCHAR(100))")
|
634
|
+
test_client.execute(f"INSERT INTO {test_table} (id, name) VALUES (1, 'test')")
|
635
|
+
|
636
|
+
try:
|
637
|
+
# Test different snapshot syntax variations
|
638
|
+
syntax_variations = [
|
639
|
+
f"CREATE SNAPSHOT test_snapshot_001 TABLE test_snapshot_db.{test_table}",
|
640
|
+
f"CREATE SNAPSHOT test_snapshot_001 OF TABLE test_snapshot_db.{test_table}",
|
641
|
+
f"CREATE SNAPSHOT test_snapshot_001 FOR TABLE test_snapshot_db.{test_table}",
|
642
|
+
f"CREATE SNAPSHOT test_snapshot_001 FROM TABLE test_snapshot_db.{test_table}",
|
643
|
+
f"CREATE SNAPSHOT test_snapshot_001 CLONE TABLE test_snapshot_db.{test_table}",
|
644
|
+
]
|
645
|
+
|
646
|
+
snapshot_created = False
|
647
|
+
for i, syntax in enumerate(syntax_variations):
|
648
|
+
try:
|
649
|
+
test_client.execute(syntax)
|
650
|
+
snapshot_created = True
|
651
|
+
print(f"✓ Syntax {i+1} worked: {syntax}")
|
652
|
+
break
|
653
|
+
except Exception as e:
|
654
|
+
print(f"✗ Syntax {i+1} failed: {e}")
|
655
|
+
|
656
|
+
if snapshot_created:
|
657
|
+
# Try to drop the snapshot
|
658
|
+
try:
|
659
|
+
test_client.execute("DROP SNAPSHOT test_snapshot_001")
|
660
|
+
print("✓ Snapshot dropped successfully")
|
661
|
+
except Exception as e:
|
662
|
+
print(f"✗ Failed to drop snapshot: {e}")
|
663
|
+
|
664
|
+
# Test listing snapshots
|
665
|
+
try:
|
666
|
+
result = test_client.execute("SHOW SNAPSHOTS")
|
667
|
+
snapshots = result.fetchall()
|
668
|
+
print(f"✓ SHOW SNAPSHOTS worked: {len(snapshots)} snapshots found")
|
669
|
+
except Exception as e:
|
670
|
+
print(f"✗ SHOW SNAPSHOTS failed: {e}")
|
671
|
+
|
672
|
+
# Test other snapshot commands
|
673
|
+
try:
|
674
|
+
result = test_client.execute("SELECT * FROM mo_catalog.mo_snapshots")
|
675
|
+
rows = result.fetchall()
|
676
|
+
print(f"✓ mo_catalog.mo_snapshots query worked: {len(rows)} rows")
|
677
|
+
except Exception as e:
|
678
|
+
print(f"✗ mo_catalog.mo_snapshots query failed: {e}")
|
679
|
+
|
680
|
+
finally:
|
681
|
+
# Clean up test table
|
682
|
+
test_client.execute(f"DROP TABLE IF EXISTS {test_table}")
|
683
|
+
|
684
|
+
# ==================== ASYNC SNAPSHOT OPERATIONS ====================
|
685
|
+
|
686
|
+
@pytest.mark.asyncio
|
687
|
+
async def test_async_snapshot_operations(self, test_async_client):
|
688
|
+
"""Test async snapshot operations - from test_snapshot_restore.py"""
|
689
|
+
# Create test data
|
690
|
+
await test_async_client.execute(
|
691
|
+
"CREATE TABLE IF NOT EXISTS snapshot_test (id INT PRIMARY KEY, name VARCHAR(50), value INT)"
|
692
|
+
)
|
693
|
+
await test_async_client.execute("DELETE FROM snapshot_test")
|
694
|
+
await test_async_client.execute("INSERT INTO snapshot_test VALUES (1, 'async_test1', 100)")
|
695
|
+
await test_async_client.execute("INSERT INTO snapshot_test VALUES (2, 'async_test2', 200)")
|
696
|
+
|
697
|
+
# Verify initial data
|
698
|
+
result = await test_async_client.execute("SELECT COUNT(*) FROM snapshot_test")
|
699
|
+
assert result.rows[0][0] == 2
|
700
|
+
|
701
|
+
# Test snapshot operations (as per examples - handle failures gracefully)
|
702
|
+
snapshot_name = f"asyncsnapshot{int(time.time())}"
|
703
|
+
|
704
|
+
# Test snapshot listing (should work even if empty)
|
705
|
+
snapshots = await test_async_client.snapshots.list()
|
706
|
+
assert isinstance(snapshots, list)
|
707
|
+
|
708
|
+
try:
|
709
|
+
snapshot = await test_async_client.snapshots.create(
|
710
|
+
name=snapshot_name,
|
711
|
+
level="table",
|
712
|
+
database=online_config.get_test_database(),
|
713
|
+
table="snapshot_test",
|
714
|
+
description="Test async snapshot",
|
715
|
+
)
|
716
|
+
assert snapshot is not None
|
717
|
+
assert snapshot.name == snapshot_name
|
718
|
+
|
719
|
+
# Test snapshot listing after creation
|
720
|
+
snapshots = await test_async_client.snapshots.list()
|
721
|
+
assert isinstance(snapshots, list)
|
722
|
+
|
723
|
+
# Test snapshot deletion
|
724
|
+
await test_async_client.snapshots.delete(snapshot_name)
|
725
|
+
|
726
|
+
except Exception as e:
|
727
|
+
# If snapshot creation fails, test the API methods still work
|
728
|
+
assert "snapshot" in str(e).lower() or "SQL" in str(e) or "syntax" in str(e).lower() # Ignore cleanup errors
|
729
|
+
|
730
|
+
await test_async_client.execute("DROP TABLE IF EXISTS snapshot_test")
|
731
|
+
|
732
|
+
@pytest.mark.asyncio
|
733
|
+
async def test_async_snapshot_query_functionality(self, test_async_client, async_test_database):
|
734
|
+
"""Test async snapshot query functionality - from test_async_client_online.py"""
|
735
|
+
test_db, test_table = async_test_database
|
736
|
+
|
737
|
+
# First create a snapshot
|
738
|
+
snapshot_name = f"test_async_client_snap_{datetime.now().strftime('%Y%m%d_%H%M%S')}"
|
739
|
+
|
740
|
+
try:
|
741
|
+
# Create snapshot
|
742
|
+
await test_async_client.snapshots.create(snapshot_name, "table", database=test_db, table=test_table)
|
743
|
+
|
744
|
+
# Test snapshot query using query builder
|
745
|
+
result = (
|
746
|
+
await test_async_client.query(f"{test_db}.{test_table}", snapshot=snapshot_name)
|
747
|
+
.select("*")
|
748
|
+
.where("value > ?", 150)
|
749
|
+
.execute()
|
750
|
+
)
|
751
|
+
|
752
|
+
rows = result.fetchall()
|
753
|
+
assert len(rows) == 2 # Should have 2 rows with value > 150
|
754
|
+
|
755
|
+
# Test snapshot query without parameters
|
756
|
+
result = (
|
757
|
+
await test_async_client.query(f"{test_db}.{test_table}", snapshot=snapshot_name).select("COUNT(*)").execute()
|
758
|
+
)
|
759
|
+
|
760
|
+
count = result.fetchone()[0]
|
761
|
+
assert count == 3
|
762
|
+
|
763
|
+
finally:
|
764
|
+
# Clean up snapshot
|
765
|
+
try:
|
766
|
+
await test_async_client.snapshots.delete(snapshot_name)
|
767
|
+
except Exception:
|
768
|
+
pass
|
769
|
+
|
770
|
+
@pytest.mark.asyncio
|
771
|
+
async def test_async_snapshot_query_syntax_validation(self, test_async_client, async_test_database):
|
772
|
+
"""Test that async snapshot queries use correct syntax - from test_async_client_online.py"""
|
773
|
+
test_db, test_table = async_test_database
|
774
|
+
snapshot_name = f"test_async_syntax_snap_{datetime.now().strftime('%Y%m%d_%H%M%S')}"
|
775
|
+
|
776
|
+
try:
|
777
|
+
# Create snapshot
|
778
|
+
await test_async_client.snapshots.create(snapshot_name, "table", database=test_db, table=test_table)
|
779
|
+
|
780
|
+
# Test that snapshot query works with proper syntax
|
781
|
+
result = (
|
782
|
+
await test_async_client.query(f"{test_db}.{test_table}", snapshot=snapshot_name)
|
783
|
+
.select("id", "name")
|
784
|
+
.where("id = ?", 1)
|
785
|
+
.execute()
|
786
|
+
)
|
787
|
+
|
788
|
+
rows = result.fetchall()
|
789
|
+
assert len(rows) == 1
|
790
|
+
assert rows[0][0] == 1
|
791
|
+
assert rows[0][1] == 'async_test1'
|
792
|
+
|
793
|
+
finally:
|
794
|
+
# Clean up snapshot
|
795
|
+
try:
|
796
|
+
await test_async_client.snapshots.delete(snapshot_name)
|
797
|
+
except Exception:
|
798
|
+
pass
|
799
|
+
|
800
|
+
@pytest.mark.asyncio
|
801
|
+
async def test_snapshot_query(self, test_async_client):
|
802
|
+
"""Test query method with snapshot parameter - from test_async_client_interfaces.py"""
|
803
|
+
# Should return a query builder with snapshot
|
804
|
+
query = test_async_client.query("test_table", snapshot="test_snapshot")
|
805
|
+
assert query is not None
|
806
|
+
assert hasattr(query, '_snapshot_name')
|
807
|
+
assert query._snapshot_name == "test_snapshot"
|
808
|
+
|
809
|
+
@pytest.mark.asyncio
|
810
|
+
async def test_snapshot_context_manager(self, test_async_client):
|
811
|
+
"""Test snapshot context manager - from test_async_client_interfaces.py"""
|
812
|
+
# Create a test table first
|
813
|
+
await test_async_client.create_table("test_snapshot_table", {"id": "int primary key", "name": "varchar(100)"})
|
814
|
+
|
815
|
+
try:
|
816
|
+
# Create a snapshot with unique name
|
817
|
+
snapshot_name = f"test_snapshot_ctx_{int(time.time())}"
|
818
|
+
await test_async_client.snapshots.create(
|
819
|
+
name=snapshot_name,
|
820
|
+
level="table",
|
821
|
+
database=online_config.get_test_database(),
|
822
|
+
table="test_snapshot_table",
|
823
|
+
)
|
824
|
+
|
825
|
+
# Test snapshot context manager
|
826
|
+
async with test_async_client.snapshot(snapshot_name) as snapshot_client:
|
827
|
+
assert snapshot_client is not None
|
828
|
+
# Should be able to execute queries
|
829
|
+
result = await snapshot_client.execute("SELECT COUNT(*) FROM test_snapshot_table")
|
830
|
+
assert result is not None
|
831
|
+
|
832
|
+
finally:
|
833
|
+
# Cleanup
|
834
|
+
try:
|
835
|
+
await test_async_client.snapshots.delete(snapshot_name)
|
836
|
+
except Exception:
|
837
|
+
pass
|
838
|
+
try:
|
839
|
+
await test_async_client.execute("DROP TABLE IF EXISTS test_snapshot_table")
|
840
|
+
except Exception:
|
841
|
+
pass
|
842
|
+
|
843
|
+
# ==================== SNAPSHOT WITH LOGGING ====================
|
844
|
+
|
845
|
+
def test_snapshot_with_logging(self, test_client):
|
846
|
+
"""Test snapshot operations with custom logging - from test_snapshot_restore.py"""
|
847
|
+
# Create logger
|
848
|
+
logger = create_default_logger()
|
849
|
+
|
850
|
+
# Create test data in the correct database
|
851
|
+
test_db = online_config.get_test_database()
|
852
|
+
test_client.execute(
|
853
|
+
f"CREATE TABLE IF NOT EXISTS {test_db}.snapshot_test (id INT PRIMARY KEY, name VARCHAR(50), value INT)"
|
854
|
+
)
|
855
|
+
test_client.execute(f"DELETE FROM {test_db}.snapshot_test")
|
856
|
+
test_client.execute(f"INSERT INTO {test_db}.snapshot_test VALUES (1, 'log_test1', 100)")
|
857
|
+
|
858
|
+
# Create snapshot with logging
|
859
|
+
snapshot_name = f"logsnapshot{int(time.time())}"
|
860
|
+
try:
|
861
|
+
snapshot = test_client.snapshots.create(
|
862
|
+
name=snapshot_name,
|
863
|
+
level="table",
|
864
|
+
database=test_db,
|
865
|
+
table="snapshot_test",
|
866
|
+
description="Test snapshot with logging",
|
867
|
+
)
|
868
|
+
assert snapshot is not None
|
869
|
+
except Exception as e:
|
870
|
+
pytest.skip(f"Snapshot creation not supported: {e}")
|
871
|
+
|
872
|
+
# Cleanup
|
873
|
+
try:
|
874
|
+
test_client.snapshots.delete(snapshot_name)
|
875
|
+
except Exception:
|
876
|
+
pass # Ignore cleanup errors
|
877
|
+
|
878
|
+
test_client.execute(f"DROP TABLE IF EXISTS {test_db}.snapshot_test")
|
879
|
+
|
880
|
+
# ==================== ORM SNAPSHOT OPERATIONS ====================
|
881
|
+
|
882
|
+
def test_orm_snapshot_queries(self, test_client):
|
883
|
+
"""Test ORM snapshot functionality - from test_matrixone_query_orm.py"""
|
884
|
+
# This test requires ORM setup which may not be available in all environments
|
885
|
+
# We'll create a simplified version that tests the basic snapshot query functionality
|
886
|
+
|
887
|
+
# Create test table for ORM-like testing in the correct database
|
888
|
+
test_db = online_config.get_test_database()
|
889
|
+
test_client.execute(
|
890
|
+
f"CREATE TABLE IF NOT EXISTS {test_db}.test_users (id INT PRIMARY KEY, name VARCHAR(100), email VARCHAR(100))"
|
891
|
+
)
|
892
|
+
test_client.execute(f"DELETE FROM {test_db}.test_users")
|
893
|
+
test_client.execute(f"INSERT INTO {test_db}.test_users VALUES (1, 'John Doe', 'john@example.com')")
|
894
|
+
test_client.execute(f"INSERT INTO {test_db}.test_users VALUES (2, 'Jane Smith', 'jane@example.com')")
|
895
|
+
|
896
|
+
try:
|
897
|
+
# Create a snapshot
|
898
|
+
snapshot_manager = SnapshotManager(test_client)
|
899
|
+
snapshot = snapshot_manager.create(
|
900
|
+
name="test_users_snapshot",
|
901
|
+
level=SnapshotLevel.TABLE,
|
902
|
+
database=test_db,
|
903
|
+
table="test_users",
|
904
|
+
)
|
905
|
+
|
906
|
+
# Query from snapshot using basic query builder
|
907
|
+
result = test_client.query(f"{test_db}.test_users", snapshot="test_users_snapshot").select("*").execute()
|
908
|
+
rows = result.fetchall()
|
909
|
+
assert len(rows) == 2
|
910
|
+
|
911
|
+
# Query with filter from snapshot
|
912
|
+
result = (
|
913
|
+
test_client.query(f"{test_db}.test_users", snapshot="test_users_snapshot")
|
914
|
+
.select("*")
|
915
|
+
.where("id = ?", 1)
|
916
|
+
.execute()
|
917
|
+
)
|
918
|
+
rows = result.fetchall()
|
919
|
+
assert len(rows) == 1
|
920
|
+
assert rows[0][1] == 'John Doe'
|
921
|
+
|
922
|
+
except Exception as e:
|
923
|
+
pytest.skip(f"ORM snapshot functionality not supported: {e}")
|
924
|
+
|
925
|
+
finally:
|
926
|
+
# Clean up snapshot
|
927
|
+
try:
|
928
|
+
test_client.execute("DROP SNAPSHOT test_users_snapshot")
|
929
|
+
except:
|
930
|
+
pass
|
931
|
+
test_client.execute(f"DROP TABLE IF EXISTS {test_db}.test_users")
|
932
|
+
|
933
|
+
@pytest.mark.asyncio
|
934
|
+
async def test_async_orm_snapshot_queries(self, test_async_client):
|
935
|
+
"""Test async ORM snapshot functionality - from test_matrixone_query_orm.py"""
|
936
|
+
# Create test table for ORM-like testing
|
937
|
+
await test_async_client.execute(
|
938
|
+
"CREATE TABLE IF NOT EXISTS test_users (id INT PRIMARY KEY, name VARCHAR(100), email VARCHAR(100))"
|
939
|
+
)
|
940
|
+
await test_async_client.execute("DELETE FROM test_users")
|
941
|
+
await test_async_client.execute("INSERT INTO test_users VALUES (1, 'John Doe', 'john@example.com')")
|
942
|
+
await test_async_client.execute("INSERT INTO test_users VALUES (2, 'Jane Smith', 'jane@example.com')")
|
943
|
+
|
944
|
+
try:
|
945
|
+
# Create a snapshot
|
946
|
+
snapshot = await test_async_client.snapshots.create(
|
947
|
+
name="test_users_snapshot_async",
|
948
|
+
level="table",
|
949
|
+
database=online_config.get_connection_params()[4], # database name
|
950
|
+
table="test_users",
|
951
|
+
)
|
952
|
+
|
953
|
+
# Query from snapshot using basic query builder
|
954
|
+
db_name = online_config.get_connection_params()[4]
|
955
|
+
result = (
|
956
|
+
await test_async_client.query(f"{db_name}.test_users", snapshot="test_users_snapshot_async")
|
957
|
+
.select("*")
|
958
|
+
.execute()
|
959
|
+
)
|
960
|
+
rows = result.fetchall()
|
961
|
+
assert len(rows) == 2
|
962
|
+
|
963
|
+
# Query with filter from snapshot
|
964
|
+
result = (
|
965
|
+
await test_async_client.query(f"{db_name}.test_users", snapshot="test_users_snapshot_async")
|
966
|
+
.select("*")
|
967
|
+
.where("id = ?", 1)
|
968
|
+
.execute()
|
969
|
+
)
|
970
|
+
rows = result.fetchall()
|
971
|
+
assert len(rows) == 1
|
972
|
+
assert rows[0][1] == 'John Doe'
|
973
|
+
|
974
|
+
except Exception as e:
|
975
|
+
pytest.skip(f"Async ORM snapshot functionality not supported: {e}")
|
976
|
+
|
977
|
+
finally:
|
978
|
+
# Clean up snapshot
|
979
|
+
try:
|
980
|
+
await test_async_client.execute("DROP SNAPSHOT test_users_snapshot_async")
|
981
|
+
except:
|
982
|
+
pass
|
983
|
+
await test_async_client.execute("DROP TABLE IF EXISTS test_users")
|
984
|
+
|
985
|
+
def test_invalid_snapshot(self, test_client):
|
986
|
+
"""Test invalid snapshot operations - from test_matrixone_query_orm.py"""
|
987
|
+
# Test querying with non-existent snapshot
|
988
|
+
try:
|
989
|
+
result = test_client.query("test_table", snapshot="non_existent_snapshot").select("*").execute()
|
990
|
+
# If this doesn't raise an exception, the result should be empty or indicate error
|
991
|
+
assert result is not None
|
992
|
+
except Exception as e:
|
993
|
+
# Expected to fail with non-existent snapshot
|
994
|
+
assert "snapshot" in str(e).lower() or "not found" in str(e).lower()
|
995
|
+
|
996
|
+
|
997
|
+
if __name__ == '__main__':
|
998
|
+
unittest.main()
|