Skip to content

Commit 7b86df7

Browse files
authored
Add IN GROUP clause to USE WORKSPACE Fusion SQL command (#93)
* Add IN GROUP clause to USE WORKSPACE * Allow workspace group in portal objects
1 parent 9bb8474 commit 7b86df7

File tree

2 files changed

+126
-27
lines changed

2 files changed

+126
-27
lines changed

singlestoredb/fusion/handlers/workspace.py

Lines changed: 72 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@
1515

1616
class UseWorkspaceHandler(SQLHandler):
1717
"""
18-
USE WORKSPACE workspace [ with_database ];
18+
USE WORKSPACE workspace [ in_group ] [ with_database ];
1919
2020
# Workspace
2121
workspace = { workspace_id | workspace_name | current_workspace }
@@ -29,6 +29,15 @@ class UseWorkspaceHandler(SQLHandler):
2929
# Current workspace
3030
current_workspace = @@CURRENT
3131
32+
# Workspace group specification
33+
in_group = IN GROUP { group_id | group_name }
34+
35+
# ID of workspace group
36+
group_id = ID '<group-id>'
37+
38+
# Name of workspace group
39+
group_name = '<group-name>'
40+
3241
# Name of database
3342
with_database = WITH DATABASE 'database-name'
3443
@@ -38,13 +47,18 @@ class UseWorkspaceHandler(SQLHandler):
3847
3948
Arguments
4049
---------
41-
* ``<workspace-id>``: The ID of the workspace to delete.
42-
* ``<workspace-name>``: The name of the workspace to delete.
50+
* ``<workspace-id>``: The ID of the workspace to use.
51+
* ``<workspace-name>``: The name of the workspace to use.
52+
* ``<group-id>``: The ID of the workspace group to search in.
53+
* ``<group-name>``: The name of the workspace group to search in.
4354
4455
Remarks
4556
-------
4657
* If you want to specify a database in the current workspace,
4758
the workspace name can be specified as ``@@CURRENT``.
59+
* Use the ``IN GROUP`` clause to specify the ID or name of the workspace
60+
group where the workspace should be found. If not specified, the current
61+
workspace group will be used.
4862
* Specify the ``WITH DATABASE`` clause to select a default
4963
database for the session.
5064
* This command only works in a notebook session in the
@@ -57,23 +71,69 @@ class UseWorkspaceHandler(SQLHandler):
5771
5872
USE WORKSPACE 'examplews' WITH DATABASE 'dbname';
5973
74+
The following command sets the workspace to ``examplews`` from a specific
75+
workspace group::
76+
77+
USE WORKSPACE 'examplews' IN GROUP 'my-workspace-group';
78+
6079
"""
6180
def run(self, params: Dict[str, Any]) -> Optional[FusionSQLResult]:
6281
from singlestoredb.notebook import portal
82+
83+
# Handle current workspace case
6384
if params['workspace'].get('current_workspace'):
6485
if params.get('with_database'):
6586
portal.default_database = params['with_database']
66-
elif params.get('with_database'):
67-
if params['workspace'].get('workspace_name'):
68-
portal.connection = params['workspace']['workspace_name'], \
69-
params['with_database']
87+
return None
88+
89+
# Get workspace name or ID
90+
workspace_name = params['workspace'].get('workspace_name')
91+
workspace_id = params['workspace'].get('workspace_id')
92+
93+
# If IN GROUP is specified, look up workspace in that group
94+
if params.get('in_group'):
95+
workspace_group = get_workspace_group(params)
96+
97+
if workspace_name:
98+
workspace = workspace_group.workspaces[workspace_name]
99+
elif workspace_id:
100+
# Find workspace by ID in the specified group
101+
workspace = next(
102+
(w for w in workspace_group.workspaces if w.id == workspace_id),
103+
None,
104+
)
105+
if workspace is None:
106+
raise KeyError(f'no workspace found with ID: {workspace_id}')
107+
108+
workspace_id = workspace.id
109+
110+
# Set workspace and database
111+
if params.get('with_database'):
112+
if params.get('in_group'):
113+
# Use 3-element tuple: (workspace_group_id, workspace_name_or_id,
114+
# database)
115+
portal.connection = ( # type: ignore[assignment]
116+
workspace_group.id,
117+
workspace_name or workspace_id,
118+
params['with_database'],
119+
)
70120
else:
71-
portal.connection = params['workspace']['workspace_id'], \
72-
params['with_database']
73-
elif params['workspace'].get('workspace_name'):
74-
portal.workspace = params['workspace']['workspace_name']
121+
# Use 2-element tuple: (workspace_name_or_id, database)
122+
portal.connection = (
123+
workspace_name or workspace_id,
124+
params['with_database'],
125+
)
75126
else:
76-
portal.workspace = params['workspace']['workspace_id']
127+
if params.get('in_group'):
128+
# Use 2-element tuple: (workspace_group_id, workspace_name_or_id)
129+
portal.workspace = ( # type: ignore[assignment]
130+
workspace_group.id,
131+
workspace_name or workspace_id,
132+
)
133+
else:
134+
# Use string: workspace_name_or_id
135+
portal.workspace = workspace_name or workspace_id
136+
77137
return None
78138

79139

singlestoredb/notebook/_portal.py

Lines changed: 54 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
from typing import List
1111
from typing import Optional
1212
from typing import Tuple
13+
from typing import Union
1314

1415
from . import _objects as obj
1516
from ..management import workspace as mgr
@@ -167,15 +168,32 @@ def workspace(self) -> obj.Workspace:
167168
return obj.workspace
168169

169170
@workspace.setter
170-
def workspace(self, name_or_id: str) -> None:
171+
def workspace(self, workspace_spec: Union[str, Tuple[str, str]]) -> None:
171172
"""Set workspace."""
172-
if re.match(
173-
r'[a-z0-9]{8}-[a-z0-9]{4}-[a-z0-9]{4}-[a-z0-9]{4}-[a-z0-9]{12}',
174-
name_or_id, flags=re.I,
175-
):
176-
w = mgr.get_workspace(name_or_id)
173+
if isinstance(workspace_spec, tuple):
174+
# 2-element tuple: (workspace_group_id, workspace_name_or_id)
175+
workspace_group_id, name_or_id = workspace_spec
176+
uuid_pattern = (
177+
r'[a-z0-9]{8}-[a-z0-9]{4}-[a-z0-9]{4}-[a-z0-9]{4}-[a-z0-9]{12}'
178+
)
179+
if re.match(uuid_pattern, name_or_id, flags=re.I):
180+
w = mgr.get_workspace(name_or_id)
181+
else:
182+
w = mgr.get_workspace_group(workspace_group_id).workspaces[
183+
name_or_id
184+
]
177185
else:
178-
w = mgr.get_workspace_group(self.workspace_group_id).workspaces[name_or_id]
186+
# String: workspace_name_or_id (existing behavior)
187+
name_or_id = workspace_spec
188+
uuid_pattern = (
189+
r'[a-z0-9]{8}-[a-z0-9]{4}-[a-z0-9]{4}-[a-z0-9]{4}-[a-z0-9]{12}'
190+
)
191+
if re.match(uuid_pattern, name_or_id, flags=re.I):
192+
w = mgr.get_workspace(name_or_id)
193+
else:
194+
w = mgr.get_workspace_group(
195+
self.workspace_group_id,
196+
).workspaces[name_or_id]
179197

180198
if w.state and w.state.lower() not in ['active', 'resumed']:
181199
raise RuntimeError('workspace is not active')
@@ -196,16 +214,37 @@ def connection(self) -> Tuple[obj.Workspace, Optional[str]]:
196214
return self.workspace, self.default_database
197215

198216
@connection.setter
199-
def connection(self, workspace_and_default_database: Tuple[str, str]) -> None:
217+
def connection(
218+
self,
219+
connection_spec: Union[Tuple[str, str], Tuple[str, str, str]],
220+
) -> None:
200221
"""Set workspace and default database name."""
201-
name_or_id, default_database = workspace_and_default_database
202-
if re.match(
203-
r'[a-z0-9]{8}-[a-z0-9]{4}-[a-z0-9]{4}-[a-z0-9]{4}-[a-z0-9]{12}',
204-
name_or_id, flags=re.I,
205-
):
206-
w = mgr.get_workspace(name_or_id)
222+
if len(connection_spec) == 3:
223+
# 3-element tuple: (workspace_group_id, workspace_name_or_id,
224+
# default_database)
225+
workspace_group_id, name_or_id, default_database = connection_spec
226+
uuid_pattern = (
227+
r'[a-z0-9]{8}-[a-z0-9]{4}-[a-z0-9]{4}-[a-z0-9]{4}-[a-z0-9]{12}'
228+
)
229+
if re.match(uuid_pattern, name_or_id, flags=re.I):
230+
w = mgr.get_workspace(name_or_id)
231+
else:
232+
w = mgr.get_workspace_group(workspace_group_id).workspaces[
233+
name_or_id
234+
]
207235
else:
208-
w = mgr.get_workspace_group(self.workspace_group_id).workspaces[name_or_id]
236+
# 2-element tuple: (workspace_name_or_id, default_database)
237+
# existing behavior
238+
name_or_id, default_database = connection_spec
239+
uuid_pattern = (
240+
r'[a-z0-9]{8}-[a-z0-9]{4}-[a-z0-9]{4}-[a-z0-9]{4}-[a-z0-9]{12}'
241+
)
242+
if re.match(uuid_pattern, name_or_id, flags=re.I):
243+
w = mgr.get_workspace(name_or_id)
244+
else:
245+
w = mgr.get_workspace_group(
246+
self.workspace_group_id,
247+
).workspaces[name_or_id]
209248

210249
if w.state and w.state.lower() not in ['active', 'resumed']:
211250
raise RuntimeError('workspace is not active')

0 commit comments

Comments
 (0)