singlestoredb 1.0.3__cp38-abi3-win_amd64.whl → 1.1.0__cp38-abi3-win_amd64.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.
Potentially problematic release.
This version of singlestoredb might be problematic. Click here for more details.
- _singlestoredb_accel.pyd +0 -0
- singlestoredb/__init__.py +1 -1
- singlestoredb/config.py +125 -0
- singlestoredb/functions/dtypes.py +5 -198
- singlestoredb/functions/ext/__init__.py +0 -1
- singlestoredb/functions/ext/asgi.py +665 -153
- singlestoredb/functions/ext/json.py +2 -2
- singlestoredb/functions/ext/mmap.py +174 -67
- singlestoredb/functions/ext/rowdat_1.py +2 -2
- singlestoredb/functions/ext/utils.py +169 -0
- singlestoredb/fusion/handler.py +109 -9
- singlestoredb/fusion/handlers/stage.py +150 -0
- singlestoredb/fusion/handlers/workspace.py +265 -4
- singlestoredb/fusion/registry.py +69 -1
- singlestoredb/http/connection.py +40 -2
- singlestoredb/management/utils.py +30 -0
- singlestoredb/management/workspace.py +209 -35
- singlestoredb/mysql/connection.py +69 -0
- singlestoredb/mysql/cursors.py +176 -4
- singlestoredb/tests/test.sql +210 -0
- singlestoredb/tests/test_connection.py +1408 -0
- singlestoredb/tests/test_ext_func.py +2 -2
- singlestoredb/tests/test_ext_func_data.py +1 -1
- singlestoredb/utils/dtypes.py +205 -0
- singlestoredb/utils/results.py +367 -14
- {singlestoredb-1.0.3.dist-info → singlestoredb-1.1.0.dist-info}/METADATA +2 -1
- {singlestoredb-1.0.3.dist-info → singlestoredb-1.1.0.dist-info}/RECORD +31 -29
- {singlestoredb-1.0.3.dist-info → singlestoredb-1.1.0.dist-info}/LICENSE +0 -0
- {singlestoredb-1.0.3.dist-info → singlestoredb-1.1.0.dist-info}/WHEEL +0 -0
- {singlestoredb-1.0.3.dist-info → singlestoredb-1.1.0.dist-info}/entry_points.txt +0 -0
- {singlestoredb-1.0.3.dist-info → singlestoredb-1.1.0.dist-info}/top_level.txt +0 -0
|
@@ -17,6 +17,22 @@ class ShowRegionsHandler(SQLHandler):
|
|
|
17
17
|
"""
|
|
18
18
|
SHOW REGIONS [ <like> ] [ <order-by> ] [ <limit> ];
|
|
19
19
|
|
|
20
|
+
Description
|
|
21
|
+
-----------
|
|
22
|
+
Show all available regions.
|
|
23
|
+
|
|
24
|
+
Remarks
|
|
25
|
+
-------
|
|
26
|
+
* ``LIKE`` specifies a pattern to match. ``%`` is a wildcard.
|
|
27
|
+
* ``ORDER BY`` specifies the column names to sort by.
|
|
28
|
+
* ``LIMIT`` indicates a maximum number of results to return.
|
|
29
|
+
|
|
30
|
+
Example
|
|
31
|
+
-------
|
|
32
|
+
Show all regions in the US::
|
|
33
|
+
|
|
34
|
+
SHOW REGIONS LIKE 'US%' ORDER BY Name;
|
|
35
|
+
|
|
20
36
|
"""
|
|
21
37
|
|
|
22
38
|
def run(self, params: Dict[str, Any]) -> Optional[FusionSQLResult]:
|
|
@@ -42,6 +58,28 @@ class ShowWorkspaceGroupsHandler(SQLHandler):
|
|
|
42
58
|
"""
|
|
43
59
|
SHOW WORKSPACE GROUPS [ <like> ] [ <extended> ] [ <order-by> ] [ <limit> ];
|
|
44
60
|
|
|
61
|
+
Description
|
|
62
|
+
-----------
|
|
63
|
+
Show workspace group information.
|
|
64
|
+
|
|
65
|
+
Remarks
|
|
66
|
+
-------
|
|
67
|
+
* ``LIKE`` specifies a pattern to match. ``%`` is a wildcard.
|
|
68
|
+
* ``EXTENDED`` indicates that extra workspace group information should
|
|
69
|
+
be returned in the result set.
|
|
70
|
+
* ``ORDER BY`` specifies the column names to sort by.
|
|
71
|
+
* ``LIMIT`` indicates a maximum number of results to return.
|
|
72
|
+
|
|
73
|
+
Example
|
|
74
|
+
-------
|
|
75
|
+
Display workspace groups that match a pattern incuding extended information::
|
|
76
|
+
|
|
77
|
+
SHOW WORKSPACE GROUPS LIKE 'Marketing%' EXTENDED ORDER BY Name;
|
|
78
|
+
|
|
79
|
+
See Also
|
|
80
|
+
--------
|
|
81
|
+
* SHOW WORKSPACES
|
|
82
|
+
|
|
45
83
|
"""
|
|
46
84
|
|
|
47
85
|
def run(self, params: Dict[str, Any]) -> Optional[FusionSQLResult]:
|
|
@@ -92,6 +130,30 @@ class ShowWorkspacesHandler(SQLHandler):
|
|
|
92
130
|
# Name of group
|
|
93
131
|
group_name = '<group-name>'
|
|
94
132
|
|
|
133
|
+
Description
|
|
134
|
+
-----------
|
|
135
|
+
Show workspaces in a workspace group.
|
|
136
|
+
|
|
137
|
+
Remarks
|
|
138
|
+
-------
|
|
139
|
+
* ``IN GROUP`` specifies the workspace group to list workspaces for. If a
|
|
140
|
+
workspace group ID is specified, you should use ``IN GROUP ID``.
|
|
141
|
+
* ``LIKE`` specifies a pattern to match. ``%`` is a wildcard.
|
|
142
|
+
* ``EXTENDED`` indicates that extra workspace group information should
|
|
143
|
+
be returned in the result set.
|
|
144
|
+
* ``ORDER BY`` specifies the column names to sort by.
|
|
145
|
+
* ``LIMIT`` indicates a maximum number of results to return.
|
|
146
|
+
|
|
147
|
+
Example
|
|
148
|
+
-------
|
|
149
|
+
Display workspaces in a workspace group including extended information::
|
|
150
|
+
|
|
151
|
+
SHOW WORKSPACES IN GROUP 'My Group' EXTENDED ORDER BY Name;
|
|
152
|
+
|
|
153
|
+
See Also
|
|
154
|
+
--------
|
|
155
|
+
* SHOW WORKSPACE GROUPS
|
|
156
|
+
|
|
95
157
|
"""
|
|
96
158
|
|
|
97
159
|
def run(self, params: Dict[str, Any]) -> Optional[FusionSQLResult]:
|
|
@@ -136,6 +198,11 @@ class CreateWorkspaceGroupHandler(SQLHandler):
|
|
|
136
198
|
[ with_password ]
|
|
137
199
|
[ expires_at ]
|
|
138
200
|
[ with_firewall_ranges ]
|
|
201
|
+
[ with_backup_bucket_kms_key_id ]
|
|
202
|
+
[ with_data_bucket_kms_key_id ]
|
|
203
|
+
[ with_smart_dr ]
|
|
204
|
+
[ allow_all_traffic ]
|
|
205
|
+
[ with_update_window ]
|
|
139
206
|
;
|
|
140
207
|
|
|
141
208
|
# Only create workspace group if it doesn't exist already
|
|
@@ -159,6 +226,60 @@ class CreateWorkspaceGroupHandler(SQLHandler):
|
|
|
159
226
|
# Incoming IP ranges
|
|
160
227
|
with_firewall_ranges = WITH FIREWALL RANGES '<ip-range>',...
|
|
161
228
|
|
|
229
|
+
# Backup bucket key
|
|
230
|
+
with_backup_bucket_kms_key_id = WITH BACKUP BUCKET KMS KEY ID '<key-id>'
|
|
231
|
+
|
|
232
|
+
# Data bucket key
|
|
233
|
+
with_data_bucket_kms_key_id = WITH DATA BUCKET KMS KEY ID '<key-id>'
|
|
234
|
+
|
|
235
|
+
# Smart DR
|
|
236
|
+
with_smart_dr = WITH SMART DR
|
|
237
|
+
|
|
238
|
+
# Allow all incoming traffic
|
|
239
|
+
allow_all_traffic = ALLOW ALL TRAFFIC
|
|
240
|
+
|
|
241
|
+
# Update window
|
|
242
|
+
with_update_window = WITH UPDATE WINDOW '<day>:<hour>'
|
|
243
|
+
|
|
244
|
+
Description
|
|
245
|
+
-----------
|
|
246
|
+
Create a workspace group.
|
|
247
|
+
|
|
248
|
+
Remarks
|
|
249
|
+
-------
|
|
250
|
+
* ``IF NOT EXISTS`` indicates that the creation of the workspace group
|
|
251
|
+
will only be attempted if a workspace group with that name doesn't
|
|
252
|
+
already exist.
|
|
253
|
+
* ``IN REGION`` specifies the region to create the workspace group in.
|
|
254
|
+
If a region ID is used, ``IN REGION ID`` should be used.
|
|
255
|
+
* ``EXPIRES AT`` specifies an expiration date/time or interval.
|
|
256
|
+
* ``WITH FIREWALL RANGES`` indicates IP ranges to allow access to the
|
|
257
|
+
workspace group.
|
|
258
|
+
* ``WITH BACKUP BUCKET KMS KEY ID`` is the key ID associated with the
|
|
259
|
+
backup bucket.
|
|
260
|
+
* ``WITH DATA BUCKET KMS KEY ID`` is the key ID associated with the
|
|
261
|
+
data bucket.
|
|
262
|
+
* ``WITH SMART DR`` enables smart disaster recovery.
|
|
263
|
+
* ``ALLOW ALL TRAFFIC`` allows all incoming traffic.
|
|
264
|
+
* ``WITH UPDATE WINDOW`` specifies tha day (0-6) and hour (0-23) of the
|
|
265
|
+
update window.
|
|
266
|
+
|
|
267
|
+
Examples
|
|
268
|
+
--------
|
|
269
|
+
Example 1: Create workspace group in US East 2 (Ohio)::
|
|
270
|
+
|
|
271
|
+
CREATE WORKSPACE GROUP 'My Group' IN REGION 'US East 2 (Ohio)';
|
|
272
|
+
|
|
273
|
+
Example 2: Create workspace group with region ID and accessible from anywhere::
|
|
274
|
+
|
|
275
|
+
CREATE WORKSPACE GROUP 'My Group'
|
|
276
|
+
IN REGION ID '93b61160-0cae-4e11-a5de-977b8e2e3ee5'
|
|
277
|
+
WITH FIREWALL RANGES '0.0.0.0/0';
|
|
278
|
+
|
|
279
|
+
See Also
|
|
280
|
+
--------
|
|
281
|
+
* SHOW WORKSPACE GROUPS
|
|
282
|
+
|
|
162
283
|
"""
|
|
163
284
|
|
|
164
285
|
def run(self, params: Dict[str, Any]) -> Optional[FusionSQLResult]:
|
|
@@ -185,12 +306,22 @@ class CreateWorkspaceGroupHandler(SQLHandler):
|
|
|
185
306
|
else:
|
|
186
307
|
region_id = params['region_id']
|
|
187
308
|
|
|
309
|
+
with_update_window = None
|
|
310
|
+
if params['with_update_window']:
|
|
311
|
+
day, hour = params['with_update_window'].split(':', 1)
|
|
312
|
+
with_update_window = dict(day=int(day), hour=int(hour))
|
|
313
|
+
|
|
188
314
|
manager.create_workspace_group(
|
|
189
315
|
params['group_name'],
|
|
190
316
|
region=region_id,
|
|
191
317
|
admin_password=params['with_password'],
|
|
192
318
|
expires_at=params['expires_at'],
|
|
193
319
|
firewall_ranges=params['with_firewall_ranges'],
|
|
320
|
+
backup_bucket_kms_key_id=params['with_backup_bucket_kms_key_id'],
|
|
321
|
+
data_bucket_kms_key_id=params['with_data_bucket_kms_key_id'],
|
|
322
|
+
smart_dr=params['with_smart_dr'],
|
|
323
|
+
allow_all_traffic=params['allow_all_traffic'],
|
|
324
|
+
update_window=with_update_window,
|
|
194
325
|
)
|
|
195
326
|
|
|
196
327
|
return None
|
|
@@ -202,7 +333,8 @@ CreateWorkspaceGroupHandler.register(overwrite=True)
|
|
|
202
333
|
class CreateWorkspaceHandler(SQLHandler):
|
|
203
334
|
"""
|
|
204
335
|
CREATE WORKSPACE [ if_not_exists ] workspace_name [ in_group ]
|
|
205
|
-
WITH SIZE size [
|
|
336
|
+
WITH SIZE size [ auto_suspend ] [ enable_kai ]
|
|
337
|
+
[ with_cache_config ] [ wait_on_active ];
|
|
206
338
|
|
|
207
339
|
# Create workspace in workspace group
|
|
208
340
|
in_group = IN GROUP { group_id | group_name }
|
|
@@ -222,9 +354,48 @@ class CreateWorkspaceHandler(SQLHandler):
|
|
|
222
354
|
# Runtime size
|
|
223
355
|
size = '<size>'
|
|
224
356
|
|
|
357
|
+
# Auto-suspend
|
|
358
|
+
auto_suspend = AUTO SUSPEND suspend_after_seconds SECONDS suspend_type
|
|
359
|
+
suspend_after_seconds = AFTER <integer>
|
|
360
|
+
suspend_type = WITH TYPE { IDLE | SCHEDULED | DISABLED }
|
|
361
|
+
|
|
362
|
+
# Enable Kai
|
|
363
|
+
enable_kai = ENABLE KAI
|
|
364
|
+
|
|
365
|
+
# Cache config
|
|
366
|
+
with_cache_config = WITH CACHE CONFIG <integer>
|
|
367
|
+
|
|
225
368
|
# Wait for workspace to be active before continuing
|
|
226
369
|
wait_on_active = WAIT ON ACTIVE
|
|
227
370
|
|
|
371
|
+
Description
|
|
372
|
+
-----------
|
|
373
|
+
Create a workspace in a workspace group.
|
|
374
|
+
|
|
375
|
+
Remarks
|
|
376
|
+
-------
|
|
377
|
+
* ``IF NOT EXISTS`` indicates that the creation of the workspace
|
|
378
|
+
will only be attempted if a workspace with that name doesn't
|
|
379
|
+
already exist.
|
|
380
|
+
* ``IN GROUP`` indicates the workspace group to create the workspace
|
|
381
|
+
in. If an ID is used, ``IN GROUP ID`` should be used.
|
|
382
|
+
* ``SIZE`` indicates a cluster size specification such as 'S-00'.
|
|
383
|
+
* ``WITH CACHE CONFIG`` specifies the multiplier for the persistent cache
|
|
384
|
+
associated with the workspace. It must be 1, 2, or 4.
|
|
385
|
+
* ``WAIT ON ACTIVE`` indicates that execution should be paused until
|
|
386
|
+
the workspace has reached the ACTIVE state.
|
|
387
|
+
|
|
388
|
+
Example
|
|
389
|
+
-------
|
|
390
|
+
Create a workspace group and wait until it is active::
|
|
391
|
+
|
|
392
|
+
CREATE WORKSPACE 'my-workspace' IN GROUP 'My Group'
|
|
393
|
+
WITH SIZE 'S-00' WAIT ON ACTIVE;
|
|
394
|
+
|
|
395
|
+
See Also
|
|
396
|
+
--------
|
|
397
|
+
* CREATE WORKSPACE GROUP
|
|
398
|
+
|
|
228
399
|
"""
|
|
229
400
|
|
|
230
401
|
def run(self, params: Dict[str, Any]) -> Optional[FusionSQLResult]:
|
|
@@ -238,8 +409,19 @@ class CreateWorkspaceHandler(SQLHandler):
|
|
|
238
409
|
except KeyError:
|
|
239
410
|
pass
|
|
240
411
|
|
|
412
|
+
auto_suspend = None
|
|
413
|
+
if params['auto_suspend']:
|
|
414
|
+
auto_suspend = dict(
|
|
415
|
+
suspend_after_seconds=params['auto_suspend'][0]['suspend_after_seconds'],
|
|
416
|
+
suspend_type=params['auto_suspend'][-1]['suspend_type'].upper(),
|
|
417
|
+
)
|
|
418
|
+
|
|
241
419
|
workspace_group.create_workspace(
|
|
242
|
-
params['workspace_name'],
|
|
420
|
+
params['workspace_name'],
|
|
421
|
+
size=params['size'],
|
|
422
|
+
auto_suspend=auto_suspend,
|
|
423
|
+
enable_kai=params['enable_kai'],
|
|
424
|
+
cache_config=params['with_cache_config'],
|
|
243
425
|
wait_on_active=params['wait_on_active'],
|
|
244
426
|
)
|
|
245
427
|
|
|
@@ -274,6 +456,21 @@ class SuspendWorkspaceHandler(SQLHandler):
|
|
|
274
456
|
# Wait for workspace to be suspended before continuing
|
|
275
457
|
wait_on_suspended = WAIT ON SUSPENDED
|
|
276
458
|
|
|
459
|
+
Description
|
|
460
|
+
-----------
|
|
461
|
+
Suspend a workspace.
|
|
462
|
+
|
|
463
|
+
Remarks
|
|
464
|
+
-------
|
|
465
|
+
* ``IN GROUP`` indicates the workspace group of the workspace.
|
|
466
|
+
If an ID is used, ``IN GROUP ID`` should be used.
|
|
467
|
+
* ``WAIT ON SUSPENDED`` indicates that execution should be paused until
|
|
468
|
+
the workspace has reached the SUSPENDED state.
|
|
469
|
+
|
|
470
|
+
See Also
|
|
471
|
+
--------
|
|
472
|
+
* RESUME WORKSPACE
|
|
473
|
+
|
|
277
474
|
"""
|
|
278
475
|
|
|
279
476
|
def run(self, params: Dict[str, Any]) -> Optional[FusionSQLResult]:
|
|
@@ -287,7 +484,8 @@ SuspendWorkspaceHandler.register(overwrite=True)
|
|
|
287
484
|
|
|
288
485
|
class ResumeWorkspaceHandler(SQLHandler):
|
|
289
486
|
"""
|
|
290
|
-
RESUME WORKSPACE workspace [ in_group ]
|
|
487
|
+
RESUME WORKSPACE workspace [ in_group ]
|
|
488
|
+
[ disable_auto_suspend ] [ wait_on_resumed ];
|
|
291
489
|
|
|
292
490
|
# Workspace
|
|
293
491
|
workspace = { workspace_id | workspace_name }
|
|
@@ -307,14 +505,31 @@ class ResumeWorkspaceHandler(SQLHandler):
|
|
|
307
505
|
# Name of workspace group
|
|
308
506
|
group_name = '<group-name>'
|
|
309
507
|
|
|
508
|
+
# Disable auto-suspend
|
|
509
|
+
disable_auto_suspend = DISABLE AUTO SUSPEND
|
|
510
|
+
|
|
310
511
|
# Wait for workspace to be resumed before continuing
|
|
311
512
|
wait_on_resumed = WAIT ON RESUMED
|
|
312
513
|
|
|
514
|
+
Description
|
|
515
|
+
-----------
|
|
516
|
+
Resume a workspace.
|
|
517
|
+
|
|
518
|
+
Remarks
|
|
519
|
+
-------
|
|
520
|
+
* ``IN GROUP`` indicates the workspace group of the workspace.
|
|
521
|
+
If an ID is used, ``IN GROUP ID`` should be used.
|
|
522
|
+
* ``WAIT ON RESUMED`` indicates that execution should be paused until
|
|
523
|
+
the workspace has reached the RESUMED state.
|
|
524
|
+
|
|
313
525
|
"""
|
|
314
526
|
|
|
315
527
|
def run(self, params: Dict[str, Any]) -> Optional[FusionSQLResult]:
|
|
316
528
|
ws = get_workspace(params)
|
|
317
|
-
ws.resume(
|
|
529
|
+
ws.resume(
|
|
530
|
+
wait_on_resumed=params['wait_on_resumed'],
|
|
531
|
+
disable_auto_suspend=params['disable_auto_suspend'],
|
|
532
|
+
)
|
|
318
533
|
return None
|
|
319
534
|
|
|
320
535
|
|
|
@@ -343,6 +558,29 @@ class DropWorkspaceGroupHandler(SQLHandler):
|
|
|
343
558
|
# Should the workspace group be terminated even if it has workspaces?
|
|
344
559
|
force = FORCE
|
|
345
560
|
|
|
561
|
+
Description
|
|
562
|
+
-----------
|
|
563
|
+
Drop a workspace group.
|
|
564
|
+
|
|
565
|
+
Remarks
|
|
566
|
+
-------
|
|
567
|
+
* ``IF EXISTS`` indicates that the dropping of the workspace group should
|
|
568
|
+
only be attempted if a workspace group with the given name exists.
|
|
569
|
+
* ``WAIT ON TERMINATED`` specifies that execution should be paused
|
|
570
|
+
until the workspace group reaches the TERMINATED state.
|
|
571
|
+
* ``FORCE`` specifies that the workspace group should be terminated
|
|
572
|
+
even if it contains workspaces.
|
|
573
|
+
|
|
574
|
+
Example
|
|
575
|
+
-------
|
|
576
|
+
Drop a workspace group and all workspaces within it::
|
|
577
|
+
|
|
578
|
+
DROP WORKSPACE GROUP 'My Group' FORCE;
|
|
579
|
+
|
|
580
|
+
See Also
|
|
581
|
+
--------
|
|
582
|
+
* DROP WORKSPACE
|
|
583
|
+
|
|
346
584
|
"""
|
|
347
585
|
|
|
348
586
|
def run(self, params: Dict[str, Any]) -> Optional[FusionSQLResult]:
|
|
@@ -393,6 +631,29 @@ class DropWorkspaceHandler(SQLHandler):
|
|
|
393
631
|
# Wait for workspace to be terminated before continuing
|
|
394
632
|
wait_on_terminated = WAIT ON TERMINATED
|
|
395
633
|
|
|
634
|
+
Description
|
|
635
|
+
-----------
|
|
636
|
+
Drop a workspace.
|
|
637
|
+
|
|
638
|
+
Remarks
|
|
639
|
+
-------
|
|
640
|
+
* ``IF EXISTS`` indicates that the dropping of the workspace should
|
|
641
|
+
only be attempted if a workspace with the given name exists.
|
|
642
|
+
* ``IN GROUP`` indicates the workspace group of the workspace.
|
|
643
|
+
If an ID is used, ``IN GROUP ID`` should be used.
|
|
644
|
+
* ``WAIT ON TERMINATED`` specifies that execution should be paused
|
|
645
|
+
until the workspace reaches the TERMINATED state.
|
|
646
|
+
|
|
647
|
+
Example
|
|
648
|
+
-------
|
|
649
|
+
Drop a workspace if it exists::
|
|
650
|
+
|
|
651
|
+
DROP WORKSPACE IF EXISTS 'my-workspace' IN GROUP 'My Group';
|
|
652
|
+
|
|
653
|
+
See Also
|
|
654
|
+
--------
|
|
655
|
+
* DROP WORKSPACE GROUP
|
|
656
|
+
|
|
396
657
|
"""
|
|
397
658
|
|
|
398
659
|
def run(self, params: Dict[str, Any]) -> Optional[FusionSQLResult]:
|
singlestoredb/fusion/registry.py
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
#!/usr/bin/env python3
|
|
2
2
|
import re
|
|
3
|
+
import sys
|
|
3
4
|
from typing import Any
|
|
4
5
|
from typing import Dict
|
|
5
6
|
from typing import List
|
|
@@ -120,6 +121,20 @@ class ShowFusionCommandsHandler(SQLHandler):
|
|
|
120
121
|
# LIKE pattern
|
|
121
122
|
like = LIKE '<pattern>'
|
|
122
123
|
|
|
124
|
+
Description
|
|
125
|
+
-----------
|
|
126
|
+
Display all Fusion SQL commands.
|
|
127
|
+
|
|
128
|
+
Remarks
|
|
129
|
+
-------
|
|
130
|
+
* ``LIKE`` indicates a pattern of commands to display. ``%`` is a wildcard.
|
|
131
|
+
|
|
132
|
+
Example
|
|
133
|
+
-------
|
|
134
|
+
Display all commands starting with 'SHOW'::
|
|
135
|
+
|
|
136
|
+
SHOW FUSION COMMANDS LIKE 'SHOW%';
|
|
137
|
+
|
|
123
138
|
"""
|
|
124
139
|
|
|
125
140
|
def run(self, params: Dict[str, Any]) -> Optional[result.FusionSQLResult]:
|
|
@@ -128,7 +143,7 @@ class ShowFusionCommandsHandler(SQLHandler):
|
|
|
128
143
|
|
|
129
144
|
data: List[Tuple[Any, ...]] = []
|
|
130
145
|
for _, v in sorted(_handlers.items()):
|
|
131
|
-
data.append((v.
|
|
146
|
+
data.append((v.syntax.lstrip(),))
|
|
132
147
|
|
|
133
148
|
res.set_rows(data)
|
|
134
149
|
|
|
@@ -148,6 +163,20 @@ class ShowFusionGrammarHandler(SQLHandler):
|
|
|
148
163
|
# Query to show grammar for
|
|
149
164
|
for_query = FOR '<query>'
|
|
150
165
|
|
|
166
|
+
Description
|
|
167
|
+
-----------
|
|
168
|
+
Show the full grammar of a Fusion SQL command for a given query.
|
|
169
|
+
|
|
170
|
+
Remarks
|
|
171
|
+
-------
|
|
172
|
+
* ``<query>`` is a string containing a Fusion SQL command.
|
|
173
|
+
|
|
174
|
+
Example
|
|
175
|
+
-------
|
|
176
|
+
Display the full grammar of the ``CREATE WORKSPACE`` command::
|
|
177
|
+
|
|
178
|
+
SHOW FUSION GRAMMAR FOR 'CREATE WORKSPACE';
|
|
179
|
+
|
|
151
180
|
"""
|
|
152
181
|
|
|
153
182
|
def run(self, params: Dict[str, Any]) -> Optional[result.FusionSQLResult]:
|
|
@@ -162,3 +191,42 @@ class ShowFusionGrammarHandler(SQLHandler):
|
|
|
162
191
|
|
|
163
192
|
|
|
164
193
|
ShowFusionGrammarHandler.register()
|
|
194
|
+
|
|
195
|
+
|
|
196
|
+
class ShowFusionHelpHandler(SQLHandler):
|
|
197
|
+
"""
|
|
198
|
+
SHOW FUSION HELP for_command;
|
|
199
|
+
|
|
200
|
+
# Command to show help for
|
|
201
|
+
for_command = FOR '<command>'
|
|
202
|
+
|
|
203
|
+
Description
|
|
204
|
+
-----------
|
|
205
|
+
Show the documentation for a Fusion SQL command.
|
|
206
|
+
|
|
207
|
+
Example
|
|
208
|
+
-------
|
|
209
|
+
Display the help for the ``CREATE WORKSPACE`` command::
|
|
210
|
+
|
|
211
|
+
SHOW FUSION HELP FOR 'CREATE WORKSPACE';
|
|
212
|
+
|
|
213
|
+
"""
|
|
214
|
+
|
|
215
|
+
def run(self, params: Dict[str, Any]) -> Optional[result.FusionSQLResult]:
|
|
216
|
+
handler = get_handler(params['for_command'])
|
|
217
|
+
if handler is not None:
|
|
218
|
+
try:
|
|
219
|
+
from IPython.display import display
|
|
220
|
+
from IPython.display import Markdown
|
|
221
|
+
display(Markdown(handler.help))
|
|
222
|
+
except Exception:
|
|
223
|
+
print(handler.help)
|
|
224
|
+
else:
|
|
225
|
+
print(
|
|
226
|
+
f'No handler found for command \'{params["for_command"]}\'',
|
|
227
|
+
file=sys.stderr,
|
|
228
|
+
)
|
|
229
|
+
return None
|
|
230
|
+
|
|
231
|
+
|
|
232
|
+
ShowFusionHelpHandler.register()
|
singlestoredb/http/connection.py
CHANGED
|
@@ -62,6 +62,7 @@ from ..utils.debug import log_query
|
|
|
62
62
|
from ..utils.mogrify import mogrify
|
|
63
63
|
from ..utils.results import Description
|
|
64
64
|
from ..utils.results import format_results
|
|
65
|
+
from ..utils.results import get_schema
|
|
65
66
|
from ..utils.results import Result
|
|
66
67
|
|
|
67
68
|
|
|
@@ -333,6 +334,7 @@ class Cursor(connection.Cursor):
|
|
|
333
334
|
self._row_idx: int = -1
|
|
334
335
|
self._result_idx: int = -1
|
|
335
336
|
self._descriptions: List[List[Description]] = []
|
|
337
|
+
self._schemas: List[Dict[str, Any]] = []
|
|
336
338
|
self.arraysize: int = get_option('results.arraysize')
|
|
337
339
|
self.rowcount: int = 0
|
|
338
340
|
self.lastrowid: Optional[int] = None
|
|
@@ -355,6 +357,14 @@ class Cursor(connection.Cursor):
|
|
|
355
357
|
return self._descriptions[self._result_idx]
|
|
356
358
|
return None
|
|
357
359
|
|
|
360
|
+
@property
|
|
361
|
+
def _schema(self) -> Optional[Any]:
|
|
362
|
+
if not self._schemas:
|
|
363
|
+
return None
|
|
364
|
+
if self._result_idx >= 0 and self._result_idx < len(self._schemas):
|
|
365
|
+
return self._schemas[self._result_idx]
|
|
366
|
+
return None
|
|
367
|
+
|
|
358
368
|
def _post(self, path: str, *args: Any, **kwargs: Any) -> requests.Response:
|
|
359
369
|
"""
|
|
360
370
|
Invoke a POST request on the HTTP connection.
|
|
@@ -460,6 +470,7 @@ class Cursor(connection.Cursor):
|
|
|
460
470
|
self._results_type = results_type
|
|
461
471
|
|
|
462
472
|
self._descriptions.append(list(mgmt_res.description))
|
|
473
|
+
self._schemas.append(get_schema(self._results_type, list(mgmt_res.description)))
|
|
463
474
|
self._results.append(list(mgmt_res.rows))
|
|
464
475
|
self.rowcount = len(self._results[-1])
|
|
465
476
|
|
|
@@ -487,6 +498,7 @@ class Cursor(connection.Cursor):
|
|
|
487
498
|
is_callproc: bool = False,
|
|
488
499
|
) -> int:
|
|
489
500
|
self._descriptions = []
|
|
501
|
+
self._schemas = []
|
|
490
502
|
self._results = []
|
|
491
503
|
self._pymy_results = []
|
|
492
504
|
self._row_idx = -1
|
|
@@ -571,6 +583,20 @@ class Cursor(connection.Cursor):
|
|
|
571
583
|
if isinstance(k, int):
|
|
572
584
|
http_converters[k] = v
|
|
573
585
|
|
|
586
|
+
# Make JSON a string for Arrow
|
|
587
|
+
if 'arrow' in self._results_type:
|
|
588
|
+
def json_to_str(x: Any) -> Optional[str]:
|
|
589
|
+
if x is None:
|
|
590
|
+
return None
|
|
591
|
+
return json.dumps(x)
|
|
592
|
+
http_converters[245] = json_to_str
|
|
593
|
+
|
|
594
|
+
# Don't convert date/times in polars
|
|
595
|
+
elif 'polars' in self._results_type:
|
|
596
|
+
http_converters.pop(7, None)
|
|
597
|
+
http_converters.pop(10, None)
|
|
598
|
+
http_converters.pop(12, None)
|
|
599
|
+
|
|
574
600
|
results = out['results']
|
|
575
601
|
|
|
576
602
|
# Convert data to Python types
|
|
@@ -616,6 +642,7 @@ class Cursor(connection.Cursor):
|
|
|
616
642
|
)
|
|
617
643
|
pymy_res.append(PyMyField(col['name'], flags, charset))
|
|
618
644
|
self._descriptions.append(description)
|
|
645
|
+
self._schemas.append(get_schema(self._results_type, description))
|
|
619
646
|
|
|
620
647
|
rows = convert_rows(result.get('rows', []), convs)
|
|
621
648
|
|
|
@@ -659,6 +686,7 @@ class Cursor(connection.Cursor):
|
|
|
659
686
|
rowcount = 0
|
|
660
687
|
if args is not None and len(args) > 0:
|
|
661
688
|
description = []
|
|
689
|
+
schema = {}
|
|
662
690
|
# Detect dataframes
|
|
663
691
|
if hasattr(args, 'itertuples'):
|
|
664
692
|
argiter = args.itertuples(index=False) # type: ignore
|
|
@@ -668,11 +696,14 @@ class Cursor(connection.Cursor):
|
|
|
668
696
|
self.execute(query, params)
|
|
669
697
|
if self._descriptions:
|
|
670
698
|
description = self._descriptions[-1]
|
|
699
|
+
if self._schemas:
|
|
700
|
+
schema = self._schemas[-1]
|
|
671
701
|
if self._rows is not None:
|
|
672
702
|
results.append(self._rows)
|
|
673
703
|
rowcount += self.rowcount
|
|
674
704
|
self._results = results
|
|
675
705
|
self._descriptions = [description for _ in range(len(results))]
|
|
706
|
+
self._schemas = [schema for _ in range(len(results))]
|
|
676
707
|
else:
|
|
677
708
|
self.execute(query)
|
|
678
709
|
rowcount += self.rowcount
|
|
@@ -721,6 +752,7 @@ class Cursor(connection.Cursor):
|
|
|
721
752
|
self._results_type,
|
|
722
753
|
self.description or [],
|
|
723
754
|
out, single=True,
|
|
755
|
+
schema=self._schema,
|
|
724
756
|
)
|
|
725
757
|
|
|
726
758
|
def fetchmany(
|
|
@@ -752,7 +784,10 @@ class Cursor(connection.Cursor):
|
|
|
752
784
|
size = max(int(size), 1)
|
|
753
785
|
out = self._rows[self._row_idx:self._row_idx+size]
|
|
754
786
|
self._row_idx += len(out)
|
|
755
|
-
return format_results(
|
|
787
|
+
return format_results(
|
|
788
|
+
self._results_type, self.description or [],
|
|
789
|
+
out, schema=self._schema,
|
|
790
|
+
)
|
|
756
791
|
|
|
757
792
|
def fetchall(self) -> Result:
|
|
758
793
|
"""
|
|
@@ -774,7 +809,10 @@ class Cursor(connection.Cursor):
|
|
|
774
809
|
return tuple()
|
|
775
810
|
out = list(self._rows[self._row_idx:])
|
|
776
811
|
self._row_idx = len(out)
|
|
777
|
-
return format_results(
|
|
812
|
+
return format_results(
|
|
813
|
+
self._results_type, self.description or [],
|
|
814
|
+
out, schema=self._schema,
|
|
815
|
+
)
|
|
778
816
|
|
|
779
817
|
def nextset(self) -> Optional[bool]:
|
|
780
818
|
"""Skip to the next available result set."""
|
|
@@ -9,6 +9,7 @@ from typing import Any
|
|
|
9
9
|
from typing import Callable
|
|
10
10
|
from typing import Dict
|
|
11
11
|
from typing import List
|
|
12
|
+
from typing import Mapping
|
|
12
13
|
from typing import Optional
|
|
13
14
|
from typing import SupportsIndex
|
|
14
15
|
from typing import TypeVar
|
|
@@ -282,3 +283,32 @@ def camel_to_snake(s: Optional[str]) -> Optional[str]:
|
|
|
282
283
|
if out and out[0] == '_':
|
|
283
284
|
return out[1:]
|
|
284
285
|
return out
|
|
286
|
+
|
|
287
|
+
|
|
288
|
+
def snake_to_camel_dict(
|
|
289
|
+
s: Optional[Mapping[str, Any]],
|
|
290
|
+
cap_first: bool = False,
|
|
291
|
+
) -> Optional[Dict[str, Any]]:
|
|
292
|
+
"""Convert snake-case keys to camel-case keys."""
|
|
293
|
+
if s is None:
|
|
294
|
+
return None
|
|
295
|
+
out = {}
|
|
296
|
+
for k, v in s.items():
|
|
297
|
+
if isinstance(s, Mapping):
|
|
298
|
+
out[str(snake_to_camel(k))] = snake_to_camel_dict(v, cap_first=cap_first)
|
|
299
|
+
else:
|
|
300
|
+
out[str(snake_to_camel(k))] = v
|
|
301
|
+
return out
|
|
302
|
+
|
|
303
|
+
|
|
304
|
+
def camel_to_snake_dict(s: Optional[Mapping[str, Any]]) -> Optional[Dict[str, Any]]:
|
|
305
|
+
"""Convert camel-case keys to snake-case keys."""
|
|
306
|
+
if s is None:
|
|
307
|
+
return None
|
|
308
|
+
out = {}
|
|
309
|
+
for k, v in s.items():
|
|
310
|
+
if isinstance(s, Mapping):
|
|
311
|
+
out[str(camel_to_snake(k))] = camel_to_snake_dict(v)
|
|
312
|
+
else:
|
|
313
|
+
out[str(camel_to_snake(k))] = v
|
|
314
|
+
return out
|