We are currently solving the following issue within our repository. Here is the issue text:
--- BEGIN ISSUE ---
Error creating role using CLI tool: please specify an attribute to alter
EdgeDB version: `1.0-alpha.2+dev.505.g5b68ec24`

Steps to reproduce:
```
# edgedb --admin create-role test
Usage: edgedb create-role [OPTIONS] ROLE_NAME

Error: please specify an attribute to alter
```
Command fails with weird error message if `OPTIONS` were no provided, even though they're supposed to be optional according to help message.
It works if 1 or more options are present: `edgedb --admin create-role --allow-login test`

Creating role inside interactive session without options works as expected:
```
modbay_beta> CREATE ROLE test;                                                                                                                                                                                         
CREATE
```

--- END ISSUE ---

Below are some code segments, each from a relevant file. One or more of these files may contain bugs.
--- BEGIN FILES ---
Path: `edb/cli/mng.py`
Content:
```
1 #
2 # This source file is part of the EdgeDB open source project.
3 #
4 # Copyright 2008-present MagicStack Inc. and the EdgeDB authors.
5 #
6 # Licensed under the Apache License, Version 2.0 (the "License");
7 # you may not use this file except in compliance with the License.
8 # You may obtain a copy of the License at
9 #
10 #     http://www.apache.org/licenses/LICENSE-2.0
11 #
12 # Unless required by applicable law or agreed to in writing, software
13 # distributed under the License is distributed on an "AS IS" BASIS,
14 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15 # See the License for the specific language governing permissions and
16 # limitations under the License.
17 #
18 
19 
20 from __future__ import annotations
21 
22 import re
23 import sys
24 import textwrap
25 
26 import click
27 
28 import edgedb
29 
30 from edb.cli import cli
31 from edb.cli import utils
32 from edb.edgeql.quote import quote_literal as ql, quote_ident as qi
33 
34 
35 @cli.group(help='Modify database configuration')
36 @click.pass_context
37 def configure(ctx):
38     utils.connect(ctx)
39 
40 
41 @configure.command(context_settings=dict(
42     ignore_unknown_options=True,
43 ))
44 @click.pass_context
45 @click.argument('parameter', type=str)
46 @click.argument('values', nargs=-1, type=click.UNPROCESSED)
47 def insert(ctx, parameter: str, values):
48     if not values:
49         raise click.UsageError(
50             'missing configuration value properties', ctx=ctx)
51 
52     try:
53         cfg_obj_name, props = _process_configure_composite_options(
54             ctx, parameter, values)
55     except NotAnObjectError as e:
56         raise click.UsageError(str(e), ctx=ctx) from None
57 
58     attrs = []
59 
60     for pn, (pval, ptype) in props.items():
61         if ptype.__type__.name == 'schema::ObjectType':
62             pval = f'(INSERT {pval})'
63         else:
64             pval = f'<{ptype.name}>{ql(pval)}'
65 
66         attrs.append(f'{qi(pn)} := {pval}')
67 
68     attrs = ',\n'.join(attrs)
69 
70     qry = textwrap.dedent(f'''\
71         CONFIGURE SYSTEM INSERT {cfg_obj_name} {{
72             {textwrap.indent(attrs, ' ' * 12).strip()}
73         }}
74     ''')
75 
76     try:
77         ctx.obj['conn'].execute(qry)
78     except edgedb.EdgeDBError as e:
79         raise click.ClickException(str(e)) from e
80     else:
81         click.echo(ctx.obj['conn']._get_last_status())
82 
83 
84 @configure.command()
85 @click.pass_context
86 @click.argument('parameter', type=str)
87 @click.argument('value', nargs=-1, type=str)
88 def set(ctx, parameter: str, value):
89     cfg_obj_name, cfg_type, cfg_card = _process_configure_scalar(
90         ctx, parameter, [])
91 
92     if cfg_card == 'ONE':
93         if len(value) > 1:
94             raise click.ClickException('too many values', ctx=ctx)
95         value = value[0]
96         val_expr = ql(value)
97     else:
98         val_expr = f'{{{", ".join(ql(v) for v in value)}}}'
99 
100     # Canonicalize the values by casting them.
101     vals = ctx.obj['conn'].fetchall(f'''
102         SELECT <str><{cfg_type.name}>{val_expr}
103     ''')
104 
105     args = []
106     for val in vals:
107         if cfg_type.is_numeric or cfg_type.is_bool:
108             args.append(val)
109         elif cfg_type.is_str:
110             args.append(ql(val))
111         else:
112             raise click.ClickException(
113                 f'cannot set {parameter}: it is not a string, numeric or bool'
114             )
115 
116     args_list = ', '.join(args)
117     args_expr = f'{{{args_list}}}'
118 
119     qry = textwrap.dedent(f'''
120         CONFIGURE SYSTEM SET {cfg_obj_name} := {args_expr}
121     ''')
122 
123     try:
124         ctx.obj['conn'].execute(qry)
125     except edgedb.EdgeDBError as e:
126         raise click.ClickException(str(e)) from e
127     else:
128         click.echo(ctx.obj['conn']._get_last_status())
129 
130 
131 @configure.command(context_settings=dict(
132     ignore_unknown_options=True,
133 ))
134 @click.pass_context
135 @click.argument('parameter', type=str)
136 @click.argument('values', nargs=-1, type=click.UNPROCESSED)
137 def reset(ctx, parameter: str, values):
138     try:
139         cfg_obj_name, props = _process_configure_composite_options(
140             ctx, parameter, values)
141     except NotAnObjectError:
142         is_scalar = True
143     else:
144         is_scalar = False
145 
146     if is_scalar:
147         cfg_obj_name, cfg_type, cfg_card = _process_configure_scalar(
148             ctx, parameter, values)
149 
150         qry = f'CONFIGURE SYSTEM RESET {cfg_obj_name}'
151     else:
152         attrs = []
153 
154         for pn, (pval, ptype) in props.items():
155             if ptype.__type__.name == 'schema::ObjectType':
156                 pval = f'.{pn} IS {pval}'
157             else:
158                 pval = f'.{pn} = <{ptype.name}>{ql(pval)}'
159 
160             attrs.append(pval)
161 
162         if attrs:
163             flt = f"FILTER {' AND '.join(attrs)}"
164         else:
165             flt = ''
166 
167         qry = textwrap.dedent(f'''
168             CONFIGURE SYSTEM RESET {cfg_obj_name} {flt}
169         ''')
170 
171     try:
172         ctx.obj['conn'].execute(qry)
173     except edgedb.EdgeDBError as e:
174         raise click.ClickException(str(e)) from e
175     else:
176         click.echo(ctx.obj['conn']._get_last_status())
177 
178 
179 class NotAnObjectError(Exception):
180     pass
181 
182 
183 def _process_configure_composite_options(ctx, parameter, values):
184     props = {}
185 
186     cfg_objects = ctx.obj['conn'].fetchall('''
187         WITH MODULE schema
188         SELECT ObjectType {
189             name
190         } FILTER .name LIKE 'cfg::%'
191     ''')
192 
193     cfg_objmap = {}
194 
195     for obj in cfg_objects:
196         _, _, obj_name = obj.name.partition('::')
197         cfg_objmap[obj_name] = obj_name
198         cfg_objmap[obj_name.lower()] = obj_name
199 
200     cfg_obj_name = cfg_objmap.get(parameter)
201     if not cfg_obj_name:
202         raise NotAnObjectError(
203             f'{parameter} is not a valid configuration object')
204 
205     cfg_props = ctx.obj['conn'].fetchall('''
206         WITH
207             MODULE schema,
208             Cfg := (SELECT ObjectType FILTER .name = <str>$typename)
209         SELECT Cfg.pointers {
210             name,
211             target: {name, __type__: {name}}
212         };
213     ''', typename=f'cfg::{cfg_obj_name}')
214 
215     cfg_prop_map = {p.name: p.target for p in cfg_props}
216 
217     for value in values:
218         v = re.match(r'--(\w+)(?: |=)(.*)', value)
219         if not v:
220             raise click.UsageError(f'unrecognized option: {value}', ctx=ctx)
221 
222         propname = v.group(1)
223         propval = v.group(2)
224 
225         proptype = cfg_prop_map.get(propname)
226         if proptype is None:
227             raise click.UsageError(
228                 f'{propname!r} is not a valid {cfg_obj_name} property',
229                 ctx=ctx)
230 
231         if propval in cfg_objmap:
232             propval = cfg_objmap[propval]
233 
234         props[propname] = (propval, proptype)
235 
236     return cfg_obj_name, props
237 
238 
239 def _process_configure_scalar(ctx, parameter, values):
240     if values:
241         raise click.UsageError(f'unexpected option: {next(iter(values))}')
242 
243     cfg_props = ctx.obj['conn'].fetchall('''
244         WITH
245             MODULE schema,
246             Cfg := (SELECT ObjectType FILTER .name = <str>$typename)
247         SELECT Cfg.pointers {
248             name,
249             cardinality,
250             target: {
251                 name,
252                 __type__: {name},
253                 is_numeric := (
254                     'std::anyreal' IN
255                         Cfg.pointers.target[IS ScalarType].ancestors.name),
256                 is_bool := (
257                     Cfg.pointers.target.name = 'std::bool'
258                     OR 'std::bool' IN
259                         Cfg.pointers.target[IS ScalarType].ancestors.name),
260                 is_str := (
261                     Cfg.pointers.target.name = 'std::str'
262                     OR any({'std::str', 'std::anyenum'} IN
263                         Cfg.pointers.target[IS ScalarType].ancestors.name)),
264             }
265         } FILTER .name = <str>$propname;
266     ''', typename=f'cfg::Config', propname=parameter)
267 
268     if len(cfg_props) == 0:
269         raise click.UsageError(
270             f'{parameter!r} is not a valid configuration parameter',
271             ctx=ctx)
272 
273     return parameter, cfg_props[0].target, cfg_props[0].cardinality
274 
275 
276 def options(options):
277     def _decorator(func):
278         for option in reversed(options):
279             func = option(func)
280         return func
281 
282     return _decorator
283 
284 
285 _role_options = [
286     click.option('--password/--no-password', default=None),
287     click.option('--password-from-stdin', is_flag=True, default=False),
288 ]
289 
290 
291 def _process_role_options(ctx, password, password_from_stdin):
292     if password is None and password_from_stdin:
293         password = True
294 
295     if password is not None:
296         if password:
297             if password_from_stdin:
298                 password_value = ql(sys.stdin.readline().strip('\r\n'))
299             elif sys.stdin.isatty():
300                 password_value = ql(click.prompt(
301                     'Password',
302                     hide_input=True,
303                     confirmation_prompt=True,
304                     type=str,
305                 ))
306             else:
307                 raise click.UsageError(
308                     'input is not a TTY, please use --password-from-stdin '
309                     'to provide the password value'
310                 )
311         else:
312             password_value = '{}'
313     else:
314         password_value = None
315 
316     alters = []
317     if password_value is not None:
318         alters.append(f'SET password := {password_value}')
319 
320     if not alters:
321         raise click.UsageError(
322             'please specify an attribute to alter', ctx=ctx,
323         )
324 
325     return alters
326 
327 
328 @cli.command(name='create-superuser-role',
329              help='Create a new database superuser')
330 @click.argument('role-name', type=str)
331 @options(_role_options)
332 @click.pass_context
333 def create_role(ctx, role_name, **kwargs):
334     utils.connect(ctx)
335 
336     attrs = ";\n".join(_process_role_options(ctx, **kwargs))
337 
338     qry = f'''
339         CREATE SUPERUSER ROLE {qi(role_name)} {{
340             {attrs}
341         }}
342     '''
343 
344     try:
345         ctx.obj['conn'].execute(qry)
346     except edgedb.EdgeDBError as e:
347         raise click.ClickException(str(e)) from e
348 
349 
350 @cli.command(name='alter-role', help='Modify role')
351 @click.argument('role-name', type=str)
352 @options(_role_options)
353 @click.pass_context
354 def alter_role(ctx, role_name, **kwargs):
355     utils.connect(ctx)
356 
357     attrs = ";\n".join(_process_role_options(ctx, **kwargs))
358 
359     qry = f'''
360         ALTER ROLE {qi(role_name)} {{
361             {attrs}
362         }}
363     '''
364 
365     try:
366         ctx.obj['conn'].execute(qry)
367     except edgedb.EdgeDBError as e:
368         raise click.ClickException(str(e)) from e
369 
370 
371 @cli.command(name='drop-role', help="Remove role")
372 @click.argument('role-name', type=str)
373 @click.pass_context
374 def drop_role(ctx, role_name, **kwargs):
375     utils.connect(ctx)
376     qry = f'''
377         DROP ROLE {qi(role_name)};
378     '''
379 
380     try:
381         ctx.obj['conn'].execute(qry)
382     except edgedb.EdgeDBError as e:
383         raise click.ClickException(str(e)) from e
384
```
--- END FILES ---

Please first localize the bug based on the issue statement, and then generate a patch according to the `git diff` format fenced by three backticks.

Here is an example:

```diff
diff --git a/examples/server_async.py b/examples/server_async.py
--- a/examples/server_async.py
+++ b/examples/server_async.py
@@ -313,4 +313,4 @@
 
 
 if __name__ == "__main__":
-    asyncio.run(run_async_server("."), debug=True)
+    asyncio.run(run_async_server(), debug=True)
diff --git a/examples/server_sync.py b/examples/server_sync.py
--- a/examples/server_sync.py
+++ b/examples/server_sync.py
@@ -313,5 +313,5 @@
 
 
 if __name__ == "__main__":
-    server = run_sync_server(".")
+    server = run_sync_server()
     server.shutdown()

```

diff --git a/edb/cli/mng.py b/edb/cli/mng.py
--- a/edb/cli/mng.py
+++ b/edb/cli/mng.py
@@ -288,7 +288,13 @@
 ]
 
 
-def _process_role_options(ctx, password, password_from_stdin):
+def _process_role_options(
+    ctx,
+    password,
+    password_from_stdin,
+    allow_empty=False
+):
+
     if password is None and password_from_stdin:
         password = True
 
@@ -318,6 +324,9 @@
         alters.append(f'SET password := {password_value}')
 
     if not alters:
+        if allow_empty:
+            return alters
+
         raise click.UsageError(
             'please specify an attribute to alter', ctx=ctx,
         )
@@ -333,12 +342,19 @@
 def create_role(ctx, role_name, **kwargs):
     utils.connect(ctx)
 
-    attrs = ";\n".join(_process_role_options(ctx, **kwargs))
+    attrs = ";\n".join(_process_role_options(ctx, allow_empty=True, **kwargs))
 
-    qry = f'''
-        CREATE SUPERUSER ROLE {qi(role_name)} {{
+    if attrs:
+        formatted_attrs = f'''
+        {{
             {attrs}
         }}
+        '''
+    else:
+        formatted_attrs = ''
+
+    qry = f'''
+        CREATE SUPERUSER ROLE {qi(role_name)} {formatted_attrs}
     '''
 
     try:

{"golden_diff": "diff --git a/edb/cli/mng.py b/edb/cli/mng.py\n--- a/edb/cli/mng.py\n+++ b/edb/cli/mng.py\n@@ -288,7 +288,13 @@\n ]\n \n \n-def _process_role_options(ctx, password, password_from_stdin):\n+def _process_role_options(\n+    ctx,\n+    password,\n+    password_from_stdin,\n+    allow_empty=False\n+):\n+\n     if password is None and password_from_stdin:\n         password = True\n \n@@ -318,6 +324,9 @@\n         alters.append(f'SET password := {password_value}')\n \n     if not alters:\n+        if allow_empty:\n+            return alters\n+\n         raise click.UsageError(\n             'please specify an attribute to alter', ctx=ctx,\n         )\n@@ -333,12 +342,19 @@\n def create_role(ctx, role_name, **kwargs):\n     utils.connect(ctx)\n \n-    attrs = \";\\n\".join(_process_role_options(ctx, **kwargs))\n+    attrs = \";\\n\".join(_process_role_options(ctx, allow_empty=True, **kwargs))\n \n-    qry = f'''\n-        CREATE SUPERUSER ROLE {qi(role_name)} {{\n+    if attrs:\n+        formatted_attrs = f'''\n+        {{\n             {attrs}\n         }}\n+        '''\n+    else:\n+        formatted_attrs = ''\n+\n+    qry = f'''\n+        CREATE SUPERUSER ROLE {qi(role_name)} {formatted_attrs}\n     '''\n \n     try:\n", "issue": "Error creating role using CLI tool: please specify an attribute to alter\nEdgeDB version: `1.0-alpha.2+dev.505.g5b68ec24`\r\n\r\nSteps to reproduce:\r\n```\r\n# edgedb --admin create-role test\r\nUsage: edgedb create-role [OPTIONS] ROLE_NAME\r\n\r\nError: please specify an attribute to alter\r\n```\r\nCommand fails with weird error message if `OPTIONS` were no provided, even though they're supposed to be optional according to help message.\r\nIt works if 1 or more options are present: `edgedb --admin create-role --allow-login test`\r\n\r\nCreating role inside interactive session without options works as expected:\r\n```\r\nmodbay_beta> CREATE ROLE test;                                                                                                                                                                                         \r\nCREATE\r\n```\n", "before_files": [{"content": "#\n# This source file is part of the EdgeDB open source project.\n#\n# Copyright 2008-present MagicStack Inc. and the EdgeDB authors.\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n#\n\n\nfrom __future__ import annotations\n\nimport re\nimport sys\nimport textwrap\n\nimport click\n\nimport edgedb\n\nfrom edb.cli import cli\nfrom edb.cli import utils\nfrom edb.edgeql.quote import quote_literal as ql, quote_ident as qi\n\n\n@cli.group(help='Modify database configuration')\n@click.pass_context\ndef configure(ctx):\n    utils.connect(ctx)\n\n\n@configure.command(context_settings=dict(\n    ignore_unknown_options=True,\n))\n@click.pass_context\n@click.argument('parameter', type=str)\n@click.argument('values', nargs=-1, type=click.UNPROCESSED)\ndef insert(ctx, parameter: str, values):\n    if not values:\n        raise click.UsageError(\n            'missing configuration value properties', ctx=ctx)\n\n    try:\n        cfg_obj_name, props = _process_configure_composite_options(\n            ctx, parameter, values)\n    except NotAnObjectError as e:\n        raise click.UsageError(str(e), ctx=ctx) from None\n\n    attrs = []\n\n    for pn, (pval, ptype) in props.items():\n        if ptype.__type__.name == 'schema::ObjectType':\n            pval = f'(INSERT {pval})'\n        else:\n            pval = f'<{ptype.name}>{ql(pval)}'\n\n        attrs.append(f'{qi(pn)} := {pval}')\n\n    attrs = ',\\n'.join(attrs)\n\n    qry = textwrap.dedent(f'''\\\n        CONFIGURE SYSTEM INSERT {cfg_obj_name} {{\n            {textwrap.indent(attrs, ' ' * 12).strip()}\n        }}\n    ''')\n\n    try:\n        ctx.obj['conn'].execute(qry)\n    except edgedb.EdgeDBError as e:\n        raise click.ClickException(str(e)) from e\n    else:\n        click.echo(ctx.obj['conn']._get_last_status())\n\n\n@configure.command()\n@click.pass_context\n@click.argument('parameter', type=str)\n@click.argument('value', nargs=-1, type=str)\ndef set(ctx, parameter: str, value):\n    cfg_obj_name, cfg_type, cfg_card = _process_configure_scalar(\n        ctx, parameter, [])\n\n    if cfg_card == 'ONE':\n        if len(value) > 1:\n            raise click.ClickException('too many values', ctx=ctx)\n        value = value[0]\n        val_expr = ql(value)\n    else:\n        val_expr = f'{{{\", \".join(ql(v) for v in value)}}}'\n\n    # Canonicalize the values by casting them.\n    vals = ctx.obj['conn'].fetchall(f'''\n        SELECT <str><{cfg_type.name}>{val_expr}\n    ''')\n\n    args = []\n    for val in vals:\n        if cfg_type.is_numeric or cfg_type.is_bool:\n            args.append(val)\n        elif cfg_type.is_str:\n            args.append(ql(val))\n        else:\n            raise click.ClickException(\n                f'cannot set {parameter}: it is not a string, numeric or bool'\n            )\n\n    args_list = ', '.join(args)\n    args_expr = f'{{{args_list}}}'\n\n    qry = textwrap.dedent(f'''\n        CONFIGURE SYSTEM SET {cfg_obj_name} := {args_expr}\n    ''')\n\n    try:\n        ctx.obj['conn'].execute(qry)\n    except edgedb.EdgeDBError as e:\n        raise click.ClickException(str(e)) from e\n    else:\n        click.echo(ctx.obj['conn']._get_last_status())\n\n\n@configure.command(context_settings=dict(\n    ignore_unknown_options=True,\n))\n@click.pass_context\n@click.argument('parameter', type=str)\n@click.argument('values', nargs=-1, type=click.UNPROCESSED)\ndef reset(ctx, parameter: str, values):\n    try:\n        cfg_obj_name, props = _process_configure_composite_options(\n            ctx, parameter, values)\n    except NotAnObjectError:\n        is_scalar = True\n    else:\n        is_scalar = False\n\n    if is_scalar:\n        cfg_obj_name, cfg_type, cfg_card = _process_configure_scalar(\n            ctx, parameter, values)\n\n        qry = f'CONFIGURE SYSTEM RESET {cfg_obj_name}'\n    else:\n        attrs = []\n\n        for pn, (pval, ptype) in props.items():\n            if ptype.__type__.name == 'schema::ObjectType':\n                pval = f'.{pn} IS {pval}'\n            else:\n                pval = f'.{pn} = <{ptype.name}>{ql(pval)}'\n\n            attrs.append(pval)\n\n        if attrs:\n            flt = f\"FILTER {' AND '.join(attrs)}\"\n        else:\n            flt = ''\n\n        qry = textwrap.dedent(f'''\n            CONFIGURE SYSTEM RESET {cfg_obj_name} {flt}\n        ''')\n\n    try:\n        ctx.obj['conn'].execute(qry)\n    except edgedb.EdgeDBError as e:\n        raise click.ClickException(str(e)) from e\n    else:\n        click.echo(ctx.obj['conn']._get_last_status())\n\n\nclass NotAnObjectError(Exception):\n    pass\n\n\ndef _process_configure_composite_options(ctx, parameter, values):\n    props = {}\n\n    cfg_objects = ctx.obj['conn'].fetchall('''\n        WITH MODULE schema\n        SELECT ObjectType {\n            name\n        } FILTER .name LIKE 'cfg::%'\n    ''')\n\n    cfg_objmap = {}\n\n    for obj in cfg_objects:\n        _, _, obj_name = obj.name.partition('::')\n        cfg_objmap[obj_name] = obj_name\n        cfg_objmap[obj_name.lower()] = obj_name\n\n    cfg_obj_name = cfg_objmap.get(parameter)\n    if not cfg_obj_name:\n        raise NotAnObjectError(\n            f'{parameter} is not a valid configuration object')\n\n    cfg_props = ctx.obj['conn'].fetchall('''\n        WITH\n            MODULE schema,\n            Cfg := (SELECT ObjectType FILTER .name = <str>$typename)\n        SELECT Cfg.pointers {\n            name,\n            target: {name, __type__: {name}}\n        };\n    ''', typename=f'cfg::{cfg_obj_name}')\n\n    cfg_prop_map = {p.name: p.target for p in cfg_props}\n\n    for value in values:\n        v = re.match(r'--(\\w+)(?: |=)(.*)', value)\n        if not v:\n            raise click.UsageError(f'unrecognized option: {value}', ctx=ctx)\n\n        propname = v.group(1)\n        propval = v.group(2)\n\n        proptype = cfg_prop_map.get(propname)\n        if proptype is None:\n            raise click.UsageError(\n                f'{propname!r} is not a valid {cfg_obj_name} property',\n                ctx=ctx)\n\n        if propval in cfg_objmap:\n            propval = cfg_objmap[propval]\n\n        props[propname] = (propval, proptype)\n\n    return cfg_obj_name, props\n\n\ndef _process_configure_scalar(ctx, parameter, values):\n    if values:\n        raise click.UsageError(f'unexpected option: {next(iter(values))}')\n\n    cfg_props = ctx.obj['conn'].fetchall('''\n        WITH\n            MODULE schema,\n            Cfg := (SELECT ObjectType FILTER .name = <str>$typename)\n        SELECT Cfg.pointers {\n            name,\n            cardinality,\n            target: {\n                name,\n                __type__: {name},\n                is_numeric := (\n                    'std::anyreal' IN\n                        Cfg.pointers.target[IS ScalarType].ancestors.name),\n                is_bool := (\n                    Cfg.pointers.target.name = 'std::bool'\n                    OR 'std::bool' IN\n                        Cfg.pointers.target[IS ScalarType].ancestors.name),\n                is_str := (\n                    Cfg.pointers.target.name = 'std::str'\n                    OR any({'std::str', 'std::anyenum'} IN\n                        Cfg.pointers.target[IS ScalarType].ancestors.name)),\n            }\n        } FILTER .name = <str>$propname;\n    ''', typename=f'cfg::Config', propname=parameter)\n\n    if len(cfg_props) == 0:\n        raise click.UsageError(\n            f'{parameter!r} is not a valid configuration parameter',\n            ctx=ctx)\n\n    return parameter, cfg_props[0].target, cfg_props[0].cardinality\n\n\ndef options(options):\n    def _decorator(func):\n        for option in reversed(options):\n            func = option(func)\n        return func\n\n    return _decorator\n\n\n_role_options = [\n    click.option('--password/--no-password', default=None),\n    click.option('--password-from-stdin', is_flag=True, default=False),\n]\n\n\ndef _process_role_options(ctx, password, password_from_stdin):\n    if password is None and password_from_stdin:\n        password = True\n\n    if password is not None:\n        if password:\n            if password_from_stdin:\n                password_value = ql(sys.stdin.readline().strip('\\r\\n'))\n            elif sys.stdin.isatty():\n                password_value = ql(click.prompt(\n                    'Password',\n                    hide_input=True,\n                    confirmation_prompt=True,\n                    type=str,\n                ))\n            else:\n                raise click.UsageError(\n                    'input is not a TTY, please use --password-from-stdin '\n                    'to provide the password value'\n                )\n        else:\n            password_value = '{}'\n    else:\n        password_value = None\n\n    alters = []\n    if password_value is not None:\n        alters.append(f'SET password := {password_value}')\n\n    if not alters:\n        raise click.UsageError(\n            'please specify an attribute to alter', ctx=ctx,\n        )\n\n    return alters\n\n\n@cli.command(name='create-superuser-role',\n             help='Create a new database superuser')\n@click.argument('role-name', type=str)\n@options(_role_options)\n@click.pass_context\ndef create_role(ctx, role_name, **kwargs):\n    utils.connect(ctx)\n\n    attrs = \";\\n\".join(_process_role_options(ctx, **kwargs))\n\n    qry = f'''\n        CREATE SUPERUSER ROLE {qi(role_name)} {{\n            {attrs}\n        }}\n    '''\n\n    try:\n        ctx.obj['conn'].execute(qry)\n    except edgedb.EdgeDBError as e:\n        raise click.ClickException(str(e)) from e\n\n\n@cli.command(name='alter-role', help='Modify role')\n@click.argument('role-name', type=str)\n@options(_role_options)\n@click.pass_context\ndef alter_role(ctx, role_name, **kwargs):\n    utils.connect(ctx)\n\n    attrs = \";\\n\".join(_process_role_options(ctx, **kwargs))\n\n    qry = f'''\n        ALTER ROLE {qi(role_name)} {{\n            {attrs}\n        }}\n    '''\n\n    try:\n        ctx.obj['conn'].execute(qry)\n    except edgedb.EdgeDBError as e:\n        raise click.ClickException(str(e)) from e\n\n\n@cli.command(name='drop-role', help=\"Remove role\")\n@click.argument('role-name', type=str)\n@click.pass_context\ndef drop_role(ctx, role_name, **kwargs):\n    utils.connect(ctx)\n    qry = f'''\n        DROP ROLE {qi(role_name)};\n    '''\n\n    try:\n        ctx.obj['conn'].execute(qry)\n    except edgedb.EdgeDBError as e:\n        raise click.ClickException(str(e)) from e\n", "path": "edb/cli/mng.py"}], "after_files": [{"content": "#\n# This source file is part of the EdgeDB open source project.\n#\n# Copyright 2008-present MagicStack Inc. and the EdgeDB authors.\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n#\n\n\nfrom __future__ import annotations\n\nimport re\nimport sys\nimport textwrap\n\nimport click\n\nimport edgedb\n\nfrom edb.cli import cli\nfrom edb.cli import utils\nfrom edb.edgeql.quote import quote_literal as ql, quote_ident as qi\n\n\n@cli.group(help='Modify database configuration')\n@click.pass_context\ndef configure(ctx):\n    utils.connect(ctx)\n\n\n@configure.command(context_settings=dict(\n    ignore_unknown_options=True,\n))\n@click.pass_context\n@click.argument('parameter', type=str)\n@click.argument('values', nargs=-1, type=click.UNPROCESSED)\ndef insert(ctx, parameter: str, values):\n    if not values:\n        raise click.UsageError(\n            'missing configuration value properties', ctx=ctx)\n\n    try:\n        cfg_obj_name, props = _process_configure_composite_options(\n            ctx, parameter, values)\n    except NotAnObjectError as e:\n        raise click.UsageError(str(e), ctx=ctx) from None\n\n    attrs = []\n\n    for pn, (pval, ptype) in props.items():\n        if ptype.__type__.name == 'schema::ObjectType':\n            pval = f'(INSERT {pval})'\n        else:\n            pval = f'<{ptype.name}>{ql(pval)}'\n\n        attrs.append(f'{qi(pn)} := {pval}')\n\n    attrs = ',\\n'.join(attrs)\n\n    qry = textwrap.dedent(f'''\\\n        CONFIGURE SYSTEM INSERT {cfg_obj_name} {{\n            {textwrap.indent(attrs, ' ' * 12).strip()}\n        }}\n    ''')\n\n    try:\n        ctx.obj['conn'].execute(qry)\n    except edgedb.EdgeDBError as e:\n        raise click.ClickException(str(e)) from e\n    else:\n        click.echo(ctx.obj['conn']._get_last_status())\n\n\n@configure.command()\n@click.pass_context\n@click.argument('parameter', type=str)\n@click.argument('value', nargs=-1, type=str)\ndef set(ctx, parameter: str, value):\n    cfg_obj_name, cfg_type, cfg_card = _process_configure_scalar(\n        ctx, parameter, [])\n\n    if cfg_card == 'ONE':\n        if len(value) > 1:\n            raise click.ClickException('too many values', ctx=ctx)\n        value = value[0]\n        val_expr = ql(value)\n    else:\n        val_expr = f'{{{\", \".join(ql(v) for v in value)}}}'\n\n    # Canonicalize the values by casting them.\n    vals = ctx.obj['conn'].fetchall(f'''\n        SELECT <str><{cfg_type.name}>{val_expr}\n    ''')\n\n    args = []\n    for val in vals:\n        if cfg_type.is_numeric or cfg_type.is_bool:\n            args.append(val)\n        elif cfg_type.is_str:\n            args.append(ql(val))\n        else:\n            raise click.ClickException(\n                f'cannot set {parameter}: it is not a string, numeric or bool'\n            )\n\n    args_list = ', '.join(args)\n    args_expr = f'{{{args_list}}}'\n\n    qry = textwrap.dedent(f'''\n        CONFIGURE SYSTEM SET {cfg_obj_name} := {args_expr}\n    ''')\n\n    try:\n        ctx.obj['conn'].execute(qry)\n    except edgedb.EdgeDBError as e:\n        raise click.ClickException(str(e)) from e\n    else:\n        click.echo(ctx.obj['conn']._get_last_status())\n\n\n@configure.command(context_settings=dict(\n    ignore_unknown_options=True,\n))\n@click.pass_context\n@click.argument('parameter', type=str)\n@click.argument('values', nargs=-1, type=click.UNPROCESSED)\ndef reset(ctx, parameter: str, values):\n    try:\n        cfg_obj_name, props = _process_configure_composite_options(\n            ctx, parameter, values)\n    except NotAnObjectError:\n        is_scalar = True\n    else:\n        is_scalar = False\n\n    if is_scalar:\n        cfg_obj_name, cfg_type, cfg_card = _process_configure_scalar(\n            ctx, parameter, values)\n\n        qry = f'CONFIGURE SYSTEM RESET {cfg_obj_name}'\n    else:\n        attrs = []\n\n        for pn, (pval, ptype) in props.items():\n            if ptype.__type__.name == 'schema::ObjectType':\n                pval = f'.{pn} IS {pval}'\n            else:\n                pval = f'.{pn} = <{ptype.name}>{ql(pval)}'\n\n            attrs.append(pval)\n\n        if attrs:\n            flt = f\"FILTER {' AND '.join(attrs)}\"\n        else:\n            flt = ''\n\n        qry = textwrap.dedent(f'''\n            CONFIGURE SYSTEM RESET {cfg_obj_name} {flt}\n        ''')\n\n    try:\n        ctx.obj['conn'].execute(qry)\n    except edgedb.EdgeDBError as e:\n        raise click.ClickException(str(e)) from e\n    else:\n        click.echo(ctx.obj['conn']._get_last_status())\n\n\nclass NotAnObjectError(Exception):\n    pass\n\n\ndef _process_configure_composite_options(ctx, parameter, values):\n    props = {}\n\n    cfg_objects = ctx.obj['conn'].fetchall('''\n        WITH MODULE schema\n        SELECT ObjectType {\n            name\n        } FILTER .name LIKE 'cfg::%'\n    ''')\n\n    cfg_objmap = {}\n\n    for obj in cfg_objects:\n        _, _, obj_name = obj.name.partition('::')\n        cfg_objmap[obj_name] = obj_name\n        cfg_objmap[obj_name.lower()] = obj_name\n\n    cfg_obj_name = cfg_objmap.get(parameter)\n    if not cfg_obj_name:\n        raise NotAnObjectError(\n            f'{parameter} is not a valid configuration object')\n\n    cfg_props = ctx.obj['conn'].fetchall('''\n        WITH\n            MODULE schema,\n            Cfg := (SELECT ObjectType FILTER .name = <str>$typename)\n        SELECT Cfg.pointers {\n            name,\n            target: {name, __type__: {name}}\n        };\n    ''', typename=f'cfg::{cfg_obj_name}')\n\n    cfg_prop_map = {p.name: p.target for p in cfg_props}\n\n    for value in values:\n        v = re.match(r'--(\\w+)(?: |=)(.*)', value)\n        if not v:\n            raise click.UsageError(f'unrecognized option: {value}', ctx=ctx)\n\n        propname = v.group(1)\n        propval = v.group(2)\n\n        proptype = cfg_prop_map.get(propname)\n        if proptype is None:\n            raise click.UsageError(\n                f'{propname!r} is not a valid {cfg_obj_name} property',\n                ctx=ctx)\n\n        if propval in cfg_objmap:\n            propval = cfg_objmap[propval]\n\n        props[propname] = (propval, proptype)\n\n    return cfg_obj_name, props\n\n\ndef _process_configure_scalar(ctx, parameter, values):\n    if values:\n        raise click.UsageError(f'unexpected option: {next(iter(values))}')\n\n    cfg_props = ctx.obj['conn'].fetchall('''\n        WITH\n            MODULE schema,\n            Cfg := (SELECT ObjectType FILTER .name = <str>$typename)\n        SELECT Cfg.pointers {\n            name,\n            cardinality,\n            target: {\n                name,\n                __type__: {name},\n                is_numeric := (\n                    'std::anyreal' IN\n                        Cfg.pointers.target[IS ScalarType].ancestors.name),\n                is_bool := (\n                    Cfg.pointers.target.name = 'std::bool'\n                    OR 'std::bool' IN\n                        Cfg.pointers.target[IS ScalarType].ancestors.name),\n                is_str := (\n                    Cfg.pointers.target.name = 'std::str'\n                    OR any({'std::str', 'std::anyenum'} IN\n                        Cfg.pointers.target[IS ScalarType].ancestors.name)),\n            }\n        } FILTER .name = <str>$propname;\n    ''', typename=f'cfg::Config', propname=parameter)\n\n    if len(cfg_props) == 0:\n        raise click.UsageError(\n            f'{parameter!r} is not a valid configuration parameter',\n            ctx=ctx)\n\n    return parameter, cfg_props[0].target, cfg_props[0].cardinality\n\n\ndef options(options):\n    def _decorator(func):\n        for option in reversed(options):\n            func = option(func)\n        return func\n\n    return _decorator\n\n\n_role_options = [\n    click.option('--password/--no-password', default=None),\n    click.option('--password-from-stdin', is_flag=True, default=False),\n]\n\n\ndef _process_role_options(\n    ctx,\n    password,\n    password_from_stdin,\n    allow_empty=False\n):\n\n    if password is None and password_from_stdin:\n        password = True\n\n    if password is not None:\n        if password:\n            if password_from_stdin:\n                password_value = ql(sys.stdin.readline().strip('\\r\\n'))\n            elif sys.stdin.isatty():\n                password_value = ql(click.prompt(\n                    'Password',\n                    hide_input=True,\n                    confirmation_prompt=True,\n                    type=str,\n                ))\n            else:\n                raise click.UsageError(\n                    'input is not a TTY, please use --password-from-stdin '\n                    'to provide the password value'\n                )\n        else:\n            password_value = '{}'\n    else:\n        password_value = None\n\n    alters = []\n    if password_value is not None:\n        alters.append(f'SET password := {password_value}')\n\n    if not alters:\n        if allow_empty:\n            return alters\n\n        raise click.UsageError(\n            'please specify an attribute to alter', ctx=ctx,\n        )\n\n    return alters\n\n\n@cli.command(name='create-superuser-role',\n             help='Create a new database superuser')\n@click.argument('role-name', type=str)\n@options(_role_options)\n@click.pass_context\ndef create_role(ctx, role_name, **kwargs):\n    utils.connect(ctx)\n\n    attrs = \";\\n\".join(_process_role_options(ctx, allow_empty=True, **kwargs))\n\n    if attrs:\n        formatted_attrs = f'''\n        {{\n            {attrs}\n        }}\n        '''\n    else:\n        formatted_attrs = ''\n\n    qry = f'''\n        CREATE SUPERUSER ROLE {qi(role_name)} {formatted_attrs}\n    '''\n\n    try:\n        ctx.obj['conn'].execute(qry)\n    except edgedb.EdgeDBError as e:\n        raise click.ClickException(str(e)) from e\n\n\n@cli.command(name='alter-role', help='Modify role')\n@click.argument('role-name', type=str)\n@options(_role_options)\n@click.pass_context\ndef alter_role(ctx, role_name, **kwargs):\n    utils.connect(ctx)\n\n    attrs = \";\\n\".join(_process_role_options(ctx, **kwargs))\n\n    qry = f'''\n        ALTER ROLE {qi(role_name)} {{\n            {attrs}\n        }}\n    '''\n\n    try:\n        ctx.obj['conn'].execute(qry)\n    except edgedb.EdgeDBError as e:\n        raise click.ClickException(str(e)) from e\n\n\n@cli.command(name='drop-role', help=\"Remove role\")\n@click.argument('role-name', type=str)\n@click.pass_context\ndef drop_role(ctx, role_name, **kwargs):\n    utils.connect(ctx)\n    qry = f'''\n        DROP ROLE {qi(role_name)};\n    '''\n\n    try:\n        ctx.obj['conn'].execute(qry)\n    except edgedb.EdgeDBError as e:\n        raise click.ClickException(str(e)) from e\n", "path": "edb/cli/mng.py"}]}