matrixone-python-sdk 0.1.4__py3-none-any.whl → 0.1.5__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.
@@ -0,0 +1,482 @@
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
+ Online tests for MatrixOne CLI diagnostic tool
19
+ """
20
+
21
+ import pytest
22
+ import io
23
+ import sys
24
+ from contextlib import redirect_stdout
25
+ from matrixone import Client
26
+ from matrixone.cli_tools import MatrixOneCLI
27
+ from .test_config import online_config
28
+
29
+
30
+ @pytest.fixture(scope="module")
31
+ def client():
32
+ """Create a MatrixOne client for testing"""
33
+ c = Client()
34
+ c.connect(
35
+ host=online_config.host,
36
+ port=online_config.port,
37
+ user=online_config.user,
38
+ password=online_config.password,
39
+ database=online_config.database,
40
+ )
41
+ yield c
42
+ c.disconnect()
43
+
44
+
45
+ @pytest.fixture(scope="module")
46
+ def cli_instance(client):
47
+ """Create a CLI instance for testing"""
48
+ cli = MatrixOneCLI(client)
49
+ cli.current_database = online_config.database
50
+ return cli
51
+
52
+
53
+ @pytest.fixture(scope="module")
54
+ def test_table(client):
55
+ """Create a test table with vector index for CLI testing"""
56
+ table_name = "cli_test_table"
57
+
58
+ # Clean up if exists
59
+ try:
60
+ client.execute(f"DROP TABLE IF EXISTS {table_name}")
61
+ except:
62
+ pass
63
+
64
+ # Create table with vector column
65
+ client.create_table(
66
+ table_name, {"id": "int", "name": "varchar(100)", "embedding": "vecf32(128)", "content": "text"}, primary_key="id"
67
+ )
68
+
69
+ # Insert test data
70
+ import random
71
+
72
+ test_data = []
73
+ for i in range(100):
74
+ test_data.append(
75
+ {
76
+ "id": i,
77
+ "name": f"item_{i}",
78
+ "embedding": [random.random() for _ in range(128)],
79
+ "content": f"test content {i}",
80
+ }
81
+ )
82
+
83
+ client.batch_insert(table_name, test_data)
84
+
85
+ # Flush table to ensure metadata is available
86
+ try:
87
+ client.moctl.flush_table(online_config.database, table_name)
88
+ except:
89
+ pass # Flush might not be available in all environments
90
+
91
+ # Create IVF index
92
+ try:
93
+ client.vector_ops.create_ivf_index(
94
+ table_name=table_name, column_name="embedding", index_name="idx_embedding_ivf", lists=10
95
+ )
96
+ except:
97
+ pass # Index creation might fail in some environments
98
+
99
+ yield table_name
100
+
101
+ # Cleanup
102
+ try:
103
+ client.execute(f"DROP TABLE IF EXISTS {table_name}")
104
+ except:
105
+ pass
106
+
107
+
108
+ @pytest.fixture(scope="module")
109
+ def test_table_with_mixed_indexes(client):
110
+ """Create a test table with both regular and UNIQUE indexes for testing"""
111
+ table_name = "cli_test_mixed_indexes"
112
+
113
+ # Clean up if exists
114
+ try:
115
+ client.execute(f"DROP TABLE IF EXISTS {table_name}")
116
+ except:
117
+ pass
118
+
119
+ # Create table with multiple columns
120
+ sql = f"""
121
+ CREATE TABLE {table_name} (
122
+ id INT PRIMARY KEY,
123
+ email VARCHAR(100) UNIQUE,
124
+ username VARCHAR(50),
125
+ category VARCHAR(50),
126
+ status INT,
127
+ UNIQUE KEY uk_username (username),
128
+ INDEX idx_category (category),
129
+ INDEX idx_status (status)
130
+ )
131
+ """
132
+ client.execute(sql)
133
+
134
+ # Insert test data
135
+ for i in range(50):
136
+ client.execute(
137
+ f"INSERT INTO {table_name} VALUES (?, ?, ?, ?, ?)", (i, f"user{i}@test.com", f"user{i}", f"cat{i % 5}", i % 3)
138
+ )
139
+
140
+ # Flush table to ensure metadata is available
141
+ try:
142
+ client.moctl.flush_table(online_config.database, table_name)
143
+ except:
144
+ pass # Flush might not be available in all environments
145
+
146
+ yield table_name
147
+
148
+ # Cleanup
149
+ try:
150
+ client.execute(f"DROP TABLE IF EXISTS {table_name}")
151
+ except:
152
+ pass
153
+
154
+
155
+ class TestCLIBasicCommands:
156
+ """Test basic CLI commands"""
157
+
158
+ def test_show_all_indexes(self, cli_instance):
159
+ """Test show_all_indexes command"""
160
+ # Capture output
161
+ f = io.StringIO()
162
+ with redirect_stdout(f):
163
+ cli_instance.onecmd("show_all_indexes")
164
+
165
+ output = f.getvalue()
166
+ # Should not error
167
+ assert "Error" not in output or output == ""
168
+
169
+ def test_sql_command(self, cli_instance):
170
+ """Test SQL command execution"""
171
+ f = io.StringIO()
172
+ with redirect_stdout(f):
173
+ cli_instance.onecmd("sql SELECT 1")
174
+
175
+ output = f.getvalue()
176
+ assert "1" in output or "returned" in output
177
+
178
+
179
+ class TestCLIIndexCommands:
180
+ """Test index-related CLI commands"""
181
+
182
+ def test_show_indexes_on_table(self, cli_instance, test_table):
183
+ """Test show_indexes command on a specific table"""
184
+ f = io.StringIO()
185
+ with redirect_stdout(f):
186
+ cli_instance.onecmd(f"show_indexes {test_table}")
187
+
188
+ output = f.getvalue()
189
+ # Should either show indexes or indicate none found
190
+ assert "Error" not in output or "No secondary indexes" in output
191
+
192
+ def test_verify_counts(self, cli_instance, test_table):
193
+ """Test verify_counts command"""
194
+ f = io.StringIO()
195
+ with redirect_stdout(f):
196
+ cli_instance.onecmd(f"verify_counts {test_table}")
197
+
198
+ output = f.getvalue()
199
+ # Should either verify successfully or indicate no indexes
200
+ assert "PASSED" in output or "No secondary indexes" in output or "Error" not in output
201
+
202
+ def test_show_indexes_with_mixed_types(self, cli_instance, test_table_with_mixed_indexes):
203
+ """Test show_indexes command on a table with both regular and UNIQUE indexes"""
204
+ f = io.StringIO()
205
+ with redirect_stdout(f):
206
+ cli_instance.onecmd(f"show_indexes {test_table_with_mixed_indexes}")
207
+
208
+ output = f.getvalue()
209
+ # Should show both regular and unique indexes
210
+ assert "Secondary Indexes" in output
211
+ # Should show multiple index tables (2 UNIQUE + 2 regular = 4 total)
212
+ assert "index tables" in output.lower() or "indexes" in output.lower()
213
+
214
+ def test_verify_counts_with_mixed_indexes(self, cli_instance, test_table_with_mixed_indexes):
215
+ """Test verify_counts on table with both regular and UNIQUE indexes"""
216
+ f = io.StringIO()
217
+ with redirect_stdout(f):
218
+ cli_instance.onecmd(f"verify_counts {test_table_with_mixed_indexes}")
219
+
220
+ output = f.getvalue()
221
+ # Should verify all index tables including UNIQUE
222
+ assert "PASSED" in output or "50 rows" in output
223
+ # Should show verification for unique indexes
224
+ assert "__mo_index_unique_" in output or "__mo_index_secondary_" in output
225
+
226
+
227
+ class TestCLIIVFCommands:
228
+ """Test IVF index related commands"""
229
+
230
+ def test_show_ivf_status(self, cli_instance):
231
+ """Test show_ivf_status command"""
232
+ f = io.StringIO()
233
+ with redirect_stdout(f):
234
+ cli_instance.onecmd("show_ivf_status")
235
+
236
+ output = f.getvalue()
237
+ # Should either show IVF indexes or indicate none found
238
+ assert "Error" not in output or "No IVF indexes" in output
239
+
240
+ def test_show_ivf_status_with_table_filter(self, cli_instance, test_table):
241
+ """Test show_ivf_status with table filter"""
242
+ f = io.StringIO()
243
+ with redirect_stdout(f):
244
+ cli_instance.onecmd(f"show_ivf_status -t {test_table}")
245
+
246
+ output = f.getvalue()
247
+ # Should complete without critical errors
248
+ assert "❌" not in output or "No IVF indexes" in output
249
+
250
+
251
+ class TestCLITableStatsCommands:
252
+ """Test table statistics commands"""
253
+
254
+ def test_show_table_stats_basic(self, cli_instance, test_table):
255
+ """Test basic table stats"""
256
+ f = io.StringIO()
257
+ with redirect_stdout(f):
258
+ cli_instance.onecmd(f"show_table_stats {test_table}")
259
+
260
+ output = f.getvalue()
261
+ # Should show table statistics or indicate no stats available
262
+ assert "Table Statistics" in output or "Objects" in output or "No statistics available" in output
263
+
264
+ def test_show_table_stats_with_tombstone(self, cli_instance, test_table):
265
+ """Test table stats with tombstone"""
266
+ f = io.StringIO()
267
+ with redirect_stdout(f):
268
+ cli_instance.onecmd(f"show_table_stats {test_table} -t")
269
+
270
+ output = f.getvalue()
271
+ # Should include statistics or indicate no stats available
272
+ assert "Table Statistics" in output or "Objects" in output or "No statistics available" in output
273
+
274
+ def test_show_table_stats_detailed(self, cli_instance, test_table):
275
+ """Test detailed table stats"""
276
+ f = io.StringIO()
277
+ with redirect_stdout(f):
278
+ cli_instance.onecmd(f"show_table_stats {test_table} -d")
279
+
280
+ output = f.getvalue()
281
+ # Should show detailed object list or indicate no stats available
282
+ assert "Detailed Table Statistics" in output or "Object Name" in output or "No statistics available" in output
283
+
284
+ def test_show_table_stats_all(self, cli_instance, test_table):
285
+ """Test table stats with all options"""
286
+ f = io.StringIO()
287
+ with redirect_stdout(f):
288
+ cli_instance.onecmd(f"show_table_stats {test_table} -a")
289
+
290
+ output = f.getvalue()
291
+ # Should show comprehensive statistics or indicate no stats available
292
+ assert "Table Statistics" in output or "Objects" in output or "No statistics available" in output
293
+
294
+
295
+ class TestCLINonInteractiveMode:
296
+ """Test non-interactive command execution"""
297
+
298
+ def test_single_command_execution(self, client):
299
+ """Test executing a single command via onecmd"""
300
+ cli = MatrixOneCLI(client)
301
+ cli.current_database = online_config.database
302
+
303
+ # Capture output
304
+ f = io.StringIO()
305
+ with redirect_stdout(f):
306
+ cli.onecmd("sql SELECT 1 as test_value")
307
+
308
+ output = f.getvalue()
309
+ assert "test_value" in output or "1" in output
310
+
311
+ def test_show_all_indexes_non_interactive(self, client):
312
+ """Test show_all_indexes in non-interactive mode"""
313
+ cli = MatrixOneCLI(client)
314
+ cli.current_database = online_config.database
315
+
316
+ f = io.StringIO()
317
+ with redirect_stdout(f):
318
+ cli.onecmd("show_all_indexes")
319
+
320
+ output = f.getvalue()
321
+ # Should complete without critical errors
322
+ assert "❌ Error" not in output or output == ""
323
+
324
+
325
+ class TestCLIErrorHandling:
326
+ """Test CLI error handling"""
327
+
328
+ def test_invalid_table_name(self, cli_instance):
329
+ """Test handling of invalid table name"""
330
+ f = io.StringIO()
331
+ with redirect_stdout(f):
332
+ cli_instance.onecmd("show_table_stats nonexistent_table_xyz_123")
333
+
334
+ output = f.getvalue()
335
+ # Should show error or no statistics message
336
+ assert "Error" in output or "No statistics" in output
337
+
338
+ def test_empty_command(self, cli_instance):
339
+ """Test handling of empty commands"""
340
+ # Should not crash
341
+ cli_instance.onecmd("")
342
+ assert True # If we get here, it didn't crash
343
+
344
+ def test_malformed_sql(self, cli_instance):
345
+ """Test handling of malformed SQL"""
346
+ f = io.StringIO()
347
+ with redirect_stdout(f):
348
+ cli_instance.onecmd("sql SELECT * FROM") # Incomplete SQL
349
+
350
+ output = f.getvalue()
351
+ # Should show error
352
+ assert "Error" in output or "❌" in output
353
+
354
+
355
+ class TestCLIFlushCommands:
356
+ """Test flush table commands"""
357
+
358
+ def test_flush_table_basic(self, cli_instance, test_table):
359
+ """Test basic flush table command"""
360
+ f = io.StringIO()
361
+ with redirect_stdout(f):
362
+ cli_instance.onecmd(f"flush_table {test_table}")
363
+
364
+ output = f.getvalue()
365
+ # Should attempt to flush (may fail due to permissions, but should not crash)
366
+ assert "Flushing table" in output or "Error" in output or "Failed" in output or "flushed" in output
367
+
368
+ def test_flush_table_with_database(self, cli_instance, test_table):
369
+ """Test flush table with database parameter"""
370
+ f = io.StringIO()
371
+ with redirect_stdout(f):
372
+ cli_instance.onecmd(f"flush_table {test_table} test")
373
+
374
+ output = f.getvalue()
375
+ # Should attempt to flush (may fail due to permissions, but should not crash)
376
+ assert "Flushing table" in output or "Error" in output or "Failed" in output or "flushed" in output
377
+
378
+ def test_flush_table_invalid_table(self, cli_instance):
379
+ """Test flush table with invalid table name"""
380
+ f = io.StringIO()
381
+ with redirect_stdout(f):
382
+ cli_instance.onecmd("flush_table nonexistent_table")
383
+
384
+ output = f.getvalue()
385
+ # Should show error for invalid table
386
+ assert "Error" in output or "Failed" in output
387
+
388
+ def test_flush_table_no_args(self, cli_instance):
389
+ """Test flush table without arguments"""
390
+ f = io.StringIO()
391
+ with redirect_stdout(f):
392
+ cli_instance.onecmd("flush_table")
393
+
394
+ output = f.getvalue()
395
+ # Should show usage error
396
+ assert "Error" in output and "required" in output
397
+
398
+ def test_flush_table_with_mixed_indexes(self, cli_instance, test_table_with_mixed_indexes):
399
+ """Test flush table with both regular and UNIQUE indexes"""
400
+ f = io.StringIO()
401
+ with redirect_stdout(f):
402
+ cli_instance.onecmd(f"flush_table {test_table_with_mixed_indexes}")
403
+
404
+ output = f.getvalue()
405
+ # Should flush main table and all index tables (UNIQUE + regular)
406
+ assert "Flushing table" in output
407
+ # Should show count of index tables (4 = 2 UNIQUE + 2 regular)
408
+ assert "index tables" in output.lower() or "flushed" in output.lower()
409
+
410
+
411
+ class TestCLIUtilityCommands:
412
+ """Test utility commands"""
413
+
414
+ def test_tables_command(self, cli_instance):
415
+ """Test tables command"""
416
+ f = io.StringIO()
417
+ with redirect_stdout(f):
418
+ cli_instance.onecmd("tables")
419
+
420
+ output = f.getvalue()
421
+ # Should show tables in current database
422
+ assert "Tables in database" in output or "No tables" in output or "Total:" in output
423
+
424
+ def test_tables_with_database(self, cli_instance):
425
+ """Test tables command with database parameter"""
426
+ f = io.StringIO()
427
+ with redirect_stdout(f):
428
+ cli_instance.onecmd("tables test")
429
+
430
+ output = f.getvalue()
431
+ # Should show tables in specified database
432
+ assert "Tables in database" in output or "No tables" in output or "Total:" in output
433
+
434
+ def test_databases_command(self, cli_instance):
435
+ """Test databases command"""
436
+ f = io.StringIO()
437
+ with redirect_stdout(f):
438
+ cli_instance.onecmd("databases")
439
+
440
+ output = f.getvalue()
441
+ # Should show databases
442
+ assert "Databases:" in output and "Total:" in output
443
+ # Should show at least 'test' database
444
+ assert "test" in output.lower() or "mo_catalog" in output.lower()
445
+
446
+
447
+ class TestCLIHelp:
448
+ """Test CLI help functionality"""
449
+
450
+ def test_help_command(self, cli_instance):
451
+ """Test help command"""
452
+ # Redirect both stdout and cli_instance.stdout
453
+ f = io.StringIO()
454
+ old_stdout = cli_instance.stdout
455
+ cli_instance.stdout = f
456
+
457
+ try:
458
+ cli_instance.onecmd("help")
459
+ output = f.getvalue()
460
+ # Should show available commands
461
+ assert len(output) > 0
462
+ finally:
463
+ cli_instance.stdout = old_stdout
464
+
465
+ def test_help_specific_command(self, cli_instance):
466
+ """Test help for specific command"""
467
+ # Redirect both stdout and cli_instance.stdout
468
+ f = io.StringIO()
469
+ old_stdout = cli_instance.stdout
470
+ cli_instance.stdout = f
471
+
472
+ try:
473
+ cli_instance.onecmd("help show_table_stats")
474
+ output = f.getvalue()
475
+ # Should show help for show_table_stats (help output goes to self.stdout)
476
+ assert len(output) > 0
477
+ finally:
478
+ cli_instance.stdout = old_stdout
479
+
480
+
481
+ if __name__ == '__main__':
482
+ pytest.main([__file__, '-v'])