Skip to content

Commit

Permalink
Expose administer statistics_update() (#8335)
Browse files Browse the repository at this point in the history
There's no reason to keep this private given that `vacuum()` is public.
Oh, and teach `vacuum()` to run `statistics_update` (i.e `VACUUM
ANALYZE`).
  • Loading branch information
elprans authored and deepbuzin committed Feb 18, 2025
1 parent f9f5472 commit 63073cc
Show file tree
Hide file tree
Showing 5 changed files with 127 additions and 44 deletions.
9 changes: 7 additions & 2 deletions docs/reference/admin/index.rst
Original file line number Diff line number Diff line change
Expand Up @@ -26,9 +26,13 @@ Administrative commands for managing Gel:

Create, remove, or alter a branch.

* :ref:`administer vacuum <ref_admin_vacuum>`
* :ref:`administer statistics_update() <ref_admin_statistics_update>`

Reclaim storage space
Update internal statistics about data.

* :ref:`administer vacuum() <ref_admin_vacuum>`

Reclaim storage space.


.. toctree::
Expand All @@ -39,4 +43,5 @@ Administrative commands for managing Gel:
configure
databases
roles
statistics_update
vacuum
52 changes: 52 additions & 0 deletions docs/reference/admin/statistics_update.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
.. versionadded:: 6.0

.. _ref_admin_statistics_update:

==============================
administer statistics_update()
==============================

:eql-statement:

Update internal statistics about data.

.. eql:synopsis::
administer statistics_update "("
[<type_link_or_property> [, ...]]
")"


Description
-----------

Updates statistics about the contents of data in the current branch.
Subsequently, the query planner uses these statistics to help determine the
most efficient execution plans for queries.

:eql:synopsis:`<type_link_or_property>`
If a type name or a path to a link or property are specified, that data
will be targeted for statistics update. If omitted, all user-accessible
data will be analyzed.


Examples
--------

Update the statistics on type ``SomeType``:

.. code-block:: edgeql
administer statistics_update(SomeType);
Update statistics of type ``SomeType`` and the link ``OtherType.ptr``.

.. code-block:: edgeql
administer statistics_update(SomeType, OtherType.ptr);
Update statistics on everything that is user-accessible in the database:

.. code-block:: edgeql
administer statistics_update();
5 changes: 5 additions & 0 deletions docs/reference/admin/vacuum.rst
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ Reclaim storage space.
administer vacuum "("
[<type_link_or_property> [, ...]]
[, full := {true | false}]
[, statistics_update := {true | false}]
")"


Expand All @@ -35,6 +36,10 @@ Cleans and reclaims storage by removing obsolete data.
and reclaimed space is kept available for reuse in the database, reducing
the rate of growth of the database.

:eql:synopsis:`statistics_update := {true | false}`
If set to ``true``, updates statistics used by the planner to determine
the most efficient way to execute queries on specified data. See also
:ref:`administer statistics_update() <ref_admin_statistics_update>`.

Examples
--------
Expand Down
13 changes: 1 addition & 12 deletions edb/server/compiler/compiler.py
Original file line number Diff line number Diff line change
Expand Up @@ -1746,18 +1746,7 @@ def _compile_ql_administer(
script_info: Optional[irast.ScriptInfo] = None,
) -> dbstate.BaseQuery:
if ql.expr.func == 'statistics_update':
if not ctx.is_testmode():
raise errors.QueryError(
'statistics_update() can only be executed in test mode',
span=ql.span)

if ql.expr.args or ql.expr.kwargs:
raise errors.QueryError(
'statistics_update() does not take arguments',
span=ql.expr.span,
)

return dbstate.MaintenanceQuery(sql=b'ANALYZE')
return ddl.administer_statistics_update(ctx, ql)
elif ql.expr.func == 'schema_repair':
return ddl.administer_repair_schema(ctx, ql)
elif ql.expr.func == 'reindex':
Expand Down
92 changes: 62 additions & 30 deletions edb/server/compiler/ddl.py
Original file line number Diff line number Diff line change
Expand Up @@ -1554,39 +1554,20 @@ def administer_reindex(
return dbstate.MaintenanceQuery(sql=block.to_string().encode("utf-8"))


def administer_vacuum(
def _identify_administer_tables_and_cols(
ctx: compiler.CompileContext,
ql: qlast.AdministerStmt,
) -> dbstate.BaseQuery:
call: qlast.FunctionCall,
) -> list[str]:
from edb.ir import ast as irast
from edb.ir import typeutils as irtypeutils
from edb.schema import objtypes as s_objtypes

# check that the kwargs are valid
kwargs: Dict[str, str] = {}
for name, val in ql.expr.kwargs.items():
if name != 'full':
raise errors.QueryError(
f'unrecognized keyword argument {name!r} for vacuum()',
span=val.span,
)
elif (
not isinstance(val, qlast.Constant)
or val.kind != qlast.ConstantKind.BOOLEAN
):
raise errors.QueryError(
f'argument {name!r} for vacuum() must be a boolean literal',
span=val.span,
)
kwargs[name] = val.value

# Next go over the args (if any) and convert paths to tables/columns
args: List[Tuple[irast.Pointer | None, s_objtypes.ObjectType]] = []
current_tx = ctx.state.current_tx()
schema = current_tx.get_schema(ctx.compiler_state.std_schema)
modaliases = current_tx.get_modaliases()

for arg in ql.expr.args:
for arg in call.args:
match arg:
case qlast.Path(
steps=[qlast.ObjectRef()],
Expand Down Expand Up @@ -1643,7 +1624,7 @@ def administer_vacuum(

tables: set[s_pointers.Pointer | s_objtypes.ObjectType] = set()

for arg, (rptr, obj) in zip(ql.expr.args, args):
for arg, (rptr, obj) in zip(call.args, args):
if not rptr:
# On a type, we just vacuum the type and its descendants
tables.update({obj} | {
Expand Down Expand Up @@ -1688,24 +1669,75 @@ def administer_vacuum(
}
tables.update(ptrclses)

tables_and_columns = [
return [
pg_common.get_backend_name(schema, table)
for table in tables
]

if kwargs.get('full', '').lower() == 'true':
options = 'FULL'
else:
options = ''

command = f'VACUUM {options} ' + ', '.join(tables_and_columns)
def administer_vacuum(
ctx: compiler.CompileContext,
ql: qlast.AdministerStmt,
) -> dbstate.BaseQuery:
# check that the kwargs are valid
kwargs: Dict[str, str] = {}
for name, val in ql.expr.kwargs.items():
if name not in ('statistics_update', 'full'):
raise errors.QueryError(
f'unrecognized keyword argument {name!r} for vacuum()',
span=val.span,
)
elif (
not isinstance(val, qlast.Constant)
or val.kind != qlast.ConstantKind.BOOLEAN
):
raise errors.QueryError(
f'argument {name!r} for vacuum() must be a boolean literal',
span=val.span,
)
kwargs[name] = val.value

option_map = {
"statistics_update": "ANALYZE",
"full": "FULL",
}
command = "VACUUM"
options = ",".join(
f"{option_map[k.lower()]} {v.upper()}"
for k, v in kwargs.items()
)
if options:
command += f" ({options})"
command += " " + ", ".join(
_identify_administer_tables_and_cols(ctx, ql.expr),
)

return dbstate.MaintenanceQuery(
sql=command.encode('utf-8'),
is_transactional=False,
)


def administer_statistics_update(
ctx: compiler.CompileContext,
ql: qlast.AdministerStmt,
) -> dbstate.BaseQuery:
for name, val in ql.expr.kwargs.items():
raise errors.QueryError(
f'unrecognized keyword argument {name!r} for statistics_update()',
span=val.span,
)

command = "ANALYZE " + ", ".join(
_identify_administer_tables_and_cols(ctx, ql.expr),
)

return dbstate.MaintenanceQuery(
sql=command.encode('utf-8'),
is_transactional=True,
)


def administer_prepare_upgrade(
ctx: compiler.CompileContext,
ql: qlast.AdministerStmt,
Expand Down

0 comments on commit 63073cc

Please sign in to comment.