diff --git a/docs/reference/admin/index.rst b/docs/reference/admin/index.rst index 57a096cf69f..f74270bb558 100644 --- a/docs/reference/admin/index.rst +++ b/docs/reference/admin/index.rst @@ -26,9 +26,13 @@ Administrative commands for managing EdgeDB: Create, remove, or alter a branch. - * :ref:`administer vacuum ` + * :ref:`administer statistics_update() ` - Reclaim storage space + Update internal statistics about data. + + * :ref:`administer vacuum() ` + + Reclaim storage space. .. toctree:: @@ -39,4 +43,5 @@ Administrative commands for managing EdgeDB: configure databases roles + statistics_update vacuum diff --git a/docs/reference/admin/statistics_update.rst b/docs/reference/admin/statistics_update.rst new file mode 100644 index 00000000000..e6016b4f143 --- /dev/null +++ b/docs/reference/admin/statistics_update.rst @@ -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 "(" + [ [, ...]] + ")" + + +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:`` + 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(); diff --git a/docs/reference/admin/vacuum.rst b/docs/reference/admin/vacuum.rst index 8f98a09b32e..b8a6c69cdb6 100644 --- a/docs/reference/admin/vacuum.rst +++ b/docs/reference/admin/vacuum.rst @@ -15,6 +15,7 @@ Reclaim storage space. administer vacuum "(" [ [, ...]] [, full := {true | false}] + [, statistics_update := {true | false}] ")" @@ -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() `. Examples -------- diff --git a/edb/server/compiler/compiler.py b/edb/server/compiler/compiler.py index ea9dd61eb4b..a2fba818511 100644 --- a/edb/server/compiler/compiler.py +++ b/edb/server/compiler/compiler.py @@ -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': diff --git a/edb/server/compiler/ddl.py b/edb/server/compiler/ddl.py index 0b0026bf08a..8c99d2d828a 100644 --- a/edb/server/compiler/ddl.py +++ b/edb/server/compiler/ddl.py @@ -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()], @@ -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} | { @@ -1688,17 +1669,48 @@ 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'), @@ -1706,6 +1718,26 @@ def administer_vacuum( ) +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,