From 535620fd302c97065c578b312075b3cc2c4e065e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alja=C5=BE=20Mur=20Er=C5=BEen?= Date: Thu, 5 Dec 2024 10:46:01 +0100 Subject: [PATCH] Grammar type annotations and a few minor refactors (#8049) - **grammar type annotations** - **squash NamedDDL into ObjectDDL** - **Refactor usage of qlast.DDL** - **generate graphviz enum hiearchy** - **fix formatting of sql docs** --- docs/reference/sql_adapter.rst | 13 ++-- edb/edgeql/ast.py | 84 ++++++++++++------------ edb/edgeql/codegen.py | 5 +- edb/edgeql/compiler/normalization.py | 11 ---- edb/edgeql/declarative.py | 2 +- edb/edgeql/desugar_group.py | 1 + edb/edgeql/parser/grammar/commondl.py | 2 +- edb/edgeql/parser/grammar/config.py | 1 + edb/edgeql/parser/grammar/ddl.py | 1 + edb/edgeql/parser/grammar/expressions.py | 60 ++++++++++++++--- edb/edgeql/parser/grammar/session.py | 6 ++ edb/edgeql/parser/grammar/start.py | 6 ++ edb/edgeql/parser/grammar/statements.py | 5 ++ edb/edgeql/tracer.py | 14 ++++ edb/schema/annos.py | 2 +- edb/schema/casts.py | 2 +- edb/schema/constraints.py | 6 +- edb/schema/ddl.py | 6 +- edb/schema/delta.py | 4 +- edb/schema/expr.py | 1 + edb/schema/expraliases.py | 2 +- edb/schema/extensions.py | 4 +- edb/schema/functions.py | 6 +- edb/schema/indexes.py | 8 +-- edb/schema/operators.py | 2 +- edb/schema/referencing.py | 8 +-- edb/server/compiler/status.py | 3 + edb/testbase/lang.py | 2 +- edb/tools/ast_inheritance_graph.py | 50 +++++++++++--- edb/tools/toy_eval_model.py | 7 +- 30 files changed, 219 insertions(+), 105 deletions(-) diff --git a/docs/reference/sql_adapter.rst b/docs/reference/sql_adapter.rst index 381c39e8cdf..cae783a1574 100644 --- a/docs/reference/sql_adapter.rst +++ b/docs/reference/sql_adapter.rst @@ -287,20 +287,25 @@ construct is mapped to PostgreSQL schema: ``UUID``. They contain the ids of the link's target type. - Multi properties are mapped to tables with two columns: + - ``source UUID``, which contains the id of the property's source object type, - ``target``, which contains values of the property. - Multi links are mapped to tables with columns: + - ``source UUID``, which contains the id of the property's source object type, - ``target UUID``, which contains the ids of the link's target object type, - one column for each link property, using the same rules as properties on - object types. + object types. - Aliases are not mapped to PostgreSQL schema. -- Globals are mapped to connection settings, prefixed with ``global ``. - For example, a ``global default::username: str`` can be set using - ``SET "global default::username" TO 'Tom'``. +- Globals are mapped to connection settings, prefixed with ``global``. + For example, a ``global default::username: str`` can be set using: + + .. code-block:: sql + + SET "global default::username" TO 'Tom'``. - Access policies are applied to object type tables when setting ``apply_access_policies_sql`` is set to ``true``. diff --git a/edb/edgeql/ast.py b/edb/edgeql/ast.py index 53f317d2636..2be8df29688 100644 --- a/edb/edgeql/ast.py +++ b/edb/edgeql/ast.py @@ -23,7 +23,6 @@ # AST classes that name-clash with classes from the typing module. import typing -import enum from edb.common import enum as s_enum from edb.common import ast, span @@ -100,6 +99,11 @@ def dump_edgeql(self) -> None: dump_edgeql(self) +class GrammarEntryPoint(Base): + """Mixin denoting nodes that are entry points for EdgeQL grammar""" + __mixin_node__ = True + + class OptionValue(Base): """An option value resulting from a syntax.""" __abstract_node__ = True @@ -135,7 +139,7 @@ def __len__(self) -> int: return len(self.options) -class Expr(Base): +class Expr(GrammarEntryPoint, Base): """Abstract parent for all query expressions.""" __abstract_node__ = True @@ -167,11 +171,15 @@ class ModuleAliasDecl(Alias): alias: typing.Optional[str] +class GroupingAtom(Base): + __abstract_node__ = True + + class BaseObjectRef(Base): __abstract_node__ = True -class ObjectRef(BaseObjectRef): +class ObjectRef(BaseObjectRef, GroupingAtom): name: str module: typing.Optional[str] = None itemclass: typing.Optional[qltypes.SchemaObjectClass] = None @@ -266,13 +274,13 @@ def integer(cls, i: int) -> Constant: return Constant(kind=ConstantKind.INTEGER, value=str(i)) -class ConstantKind(enum.IntEnum): - STRING = 0 - BOOLEAN = 1 - INTEGER = 2 - FLOAT = 3 - BIGINT = 4 - DECIMAL = 5 +class ConstantKind(s_enum.StrEnum): + STRING = 'STRING' + BOOLEAN = 'BOOLEAN' + INTEGER = 'INTEGER' + FLOAT = 'FLOAT' + BIGINT = 'BIGINT' + DECIMAL = 'DECIMAL' class BytesConstant(BaseConstant): @@ -360,7 +368,7 @@ class Splat(Base): PathElement = typing.Union[Expr, Ptr, TypeIntersection, ObjectRef, Splat] -class Path(Expr): +class Path(Expr, GroupingAtom): steps: typing.List[PathElement] partial: bool = False allow_factoring: bool = False @@ -411,7 +419,7 @@ class Set(Expr): # -class Command(Base): +class Command(GrammarEntryPoint, Base): """ A top-level node that is evaluated by our server and cannot be a part of a sub expression. @@ -493,7 +501,7 @@ class Shape(Expr): allow_factoring: bool = False -class Query(Expr): +class Query(Expr, GrammarEntryPoint): __abstract_node__ = True aliases: typing.Optional[typing.List[Alias]] = None @@ -518,11 +526,8 @@ class SelectQuery(Query): implicit: bool = False -class GroupingIdentList(Base): - elements: typing.Tuple[typing.Union[GroupingAtom], ...] - - -GroupingAtom = typing.Union[ObjectRef, Path, GroupingIdentList] +class GroupingIdentList(GroupingAtom, Base): + elements: typing.Tuple[GroupingAtom, ...] class GroupingElement(Base): @@ -550,7 +555,13 @@ class GroupQuery(Query): subject: Expr -class InternalGroupQuery(GroupQuery): +class InternalGroupQuery(Query): + subject_alias: typing.Optional[str] = None + using: typing.Optional[typing.List[AliasedExpr]] + by: typing.List[GroupingElement] + + subject: Expr + group_alias: str grouping_alias: typing.Optional[str] from_desugaring: bool = False @@ -648,9 +659,8 @@ class ReleaseSavepoint(Transaction): class DDL(Base): - '''Abstract parent for all DDL statements.''' - - __abstract_node__ = True + '''A mixin denoting DDL nodes.''' + __mixin_node__ = True class Position(DDL): @@ -665,11 +675,9 @@ class DDLOperation(DDL): commands: typing.List[DDLOperation] = ast.field(factory=list) -class DDLCommand(DDLOperation): +class DDLCommand(DDLOperation, Command): __abstract_node__ = True - aliases: typing.Optional[typing.List[Alias]] = None - class NonTransactionalDDLCommand(DDLCommand): __abstract_node__ = True @@ -722,13 +730,10 @@ class SetPointerOptionality(SetField): fill_expr: typing.Optional[Expr] = None -class NamedDDL(DDLCommand): +class ObjectDDL(DDLCommand): __abstract_node__ = True - name: ObjectRef - -class ObjectDDL(NamedDDL): - __abstract_node__ = True + name: ObjectRef class CreateObject(ObjectDDL): @@ -757,7 +762,7 @@ class CreateExtendingObject(CreateObject): bases: typing.List[TypeName] -class Rename(NamedDDL): +class Rename(ObjectDDL): new_name: ObjectRef @property @@ -776,7 +781,7 @@ class MigrationCommand(DDLCommand): __abstract_node__ = True -class CreateMigration(CreateObject, MigrationCommand): +class CreateMigration(CreateObject, MigrationCommand, GrammarEntryPoint): body: NestedQLBlock parent: typing.Optional[ObjectRef] = None @@ -1246,9 +1251,6 @@ class DropIndex(DropObject, IndexCommand): class IndexMatchCommand(ObjectDDL): __abstract_node__ = True - __rust_ignore__ = True - object_class: qltypes.SchemaObjectClass = \ - qltypes.SchemaObjectClass.INDEX_MATCH valid_type: TypeName @@ -1535,20 +1537,20 @@ class AdministerStmt(Command): class SDL(Base): - '''Abstract parent for all SDL statements.''' + '''A mixin denoting SDL nodes.''' - __abstract_node__ = True + __mixin_node__ = True class ModuleDeclaration(SDL): # The 'name' is treated same as in CreateModule, for consistency, # since this declaration also implies creating a module. name: ObjectRef - declarations: typing.List[typing.Union[NamedDDL, ModuleDeclaration]] + declarations: typing.List[typing.Union[ObjectDDL, ModuleDeclaration]] -class Schema(SDL): - declarations: typing.List[typing.Union[NamedDDL, ModuleDeclaration]] +class Schema(SDL, GrammarEntryPoint, Base): + declarations: typing.List[typing.Union[ObjectDDL, ModuleDeclaration]] # @@ -1635,4 +1637,4 @@ def has_ddl_subcommand( ) # A node that can have a WITH block -Statement = Query | Command | DDLCommand +Statement = Query | Command diff --git a/edb/edgeql/codegen.py b/edb/edgeql/codegen.py index 5e82472ff16..6e48e657540 100644 --- a/edb/edgeql/codegen.py +++ b/edb/edgeql/codegen.py @@ -369,7 +369,9 @@ def visit_GroupingOperation(self, node: qlast.GroupingOperation) -> None: self.write(')') def visit_GroupQuery( - self, node: qlast.GroupQuery, no_paren: bool = False + self, + node: qlast.GroupQuery | qlast.InternalGroupQuery, + no_paren: bool = False ) -> None: # need to parenthesise when GROUP appears as an expression parenthesise = self._needs_parentheses(node) and not no_paren @@ -904,6 +906,7 @@ def _ddl_visit_body( commands = self._ddl_clean_up_commands(commands) if len(commands) == 1 and allow_short and not ( isinstance(commands[0], qlast.ObjectDDL) + and not isinstance(commands[0], qlast.Rename) ): self.write(' ') self.visit(commands[0]) diff --git a/edb/edgeql/compiler/normalization.py b/edb/edgeql/compiler/normalization.py index 645eef5bed0..453c2659bf7 100644 --- a/edb/edgeql/compiler/normalization.py +++ b/edb/edgeql/compiler/normalization.py @@ -187,17 +187,6 @@ def normalize_ObjectRef( ref.module = modaliases[ref.module] -@normalize.register -def normalize_DDL( - node: qlast.DDL, - *, - schema: s_schema.Schema, - modaliases: Mapping[Optional[str], str], - localnames: AbstractSet[str] = frozenset(), -) -> None: - raise AssertionError(f'normalize: cannot handle {node!r}') - - def _normalize_with_block( node: qlast.Query, *, diff --git a/edb/edgeql/declarative.py b/edb/edgeql/declarative.py index ad1737336fa..49b015eefb6 100644 --- a/edb/edgeql/declarative.py +++ b/edb/edgeql/declarative.py @@ -368,7 +368,7 @@ def __init__( def sdl_to_ddl( schema: s_schema.Schema, - documents: Mapping[str, List[qlast.DDL]], + documents: Mapping[str, List[qlast.DDLCommand]], ) -> Tuple[qlast.DDLCommand, ...]: ddlgraph: DDLGraph = {} diff --git a/edb/edgeql/desugar_group.py b/edb/edgeql/desugar_group.py index d6efa693f4e..611d0565d0a 100644 --- a/edb/edgeql/desugar_group.py +++ b/edb/edgeql/desugar_group.py @@ -120,6 +120,7 @@ def rewrite_atom(el: qlast.GroupingAtom) -> qlast.GroupingAtom: ) return qlast.ObjectRef(name=alias) else: + assert isinstance(el, qlast.GroupingIdentList) return qlast.GroupingIdentList( span=el.span, elements=tuple(rewrite_atom(at) for at in el.elements), diff --git a/edb/edgeql/parser/grammar/commondl.py b/edb/edgeql/parser/grammar/commondl.py index 14a63c2b4e3..643e3a5e1b4 100644 --- a/edb/edgeql/parser/grammar/commondl.py +++ b/edb/edgeql/parser/grammar/commondl.py @@ -54,7 +54,7 @@ def _parse_language(node): def _validate_declarations( declarations: typing.Sequence[ - typing.Union[qlast.ModuleDeclaration, qlast.NamedDDL]] + typing.Union[qlast.ModuleDeclaration, qlast.ObjectDDL]] ) -> None: # Check that top-level declarations either use fully-qualified # names or are module blocks. diff --git a/edb/edgeql/parser/grammar/config.py b/edb/edgeql/parser/grammar/config.py index 9adb1f7ca7c..3e8fdaf6035 100644 --- a/edb/edgeql/parser/grammar/config.py +++ b/edb/edgeql/parser/grammar/config.py @@ -47,6 +47,7 @@ def reduce_INSTANCE(self, _): class ConfigOp(Nonterm): + val: qlast.ConfigOp def reduce_SET_NodeName_ASSIGN_Expr(self, _s, name, _a, expr): self.val = qlast.ConfigSet( diff --git a/edb/edgeql/parser/grammar/ddl.py b/edb/edgeql/parser/grammar/ddl.py index 72bbb77fa82..d6e6b73ec20 100644 --- a/edb/edgeql/parser/grammar/ddl.py +++ b/edb/edgeql/parser/grammar/ddl.py @@ -52,6 +52,7 @@ class DDLStmt(Nonterm): + val: qlast.DDLCommand @parsing.inline(0) def reduce_DatabaseStmt(self, *_): diff --git a/edb/edgeql/parser/grammar/expressions.py b/edb/edgeql/parser/grammar/expressions.py index 38e77f6c88a..b02cc54c581 100644 --- a/edb/edgeql/parser/grammar/expressions.py +++ b/edb/edgeql/parser/grammar/expressions.py @@ -46,6 +46,8 @@ class ListNonterm(parsing.ListNonterm, element=None, is_internal=True): class ExprStmt(Nonterm): + val: qlast.Query + def reduce_WithBlock_ExprStmtCore(self, *kids): self.val = kids[1].val self.val.aliases = kids[0].val.aliases @@ -56,6 +58,8 @@ def reduce_ExprStmtCore(self, *kids): class ExprStmtCore(Nonterm): + val: qlast.Query + @parsing.inline(0) def reduce_SimpleFor(self, *kids): pass @@ -86,11 +90,21 @@ def reduce_SimpleDelete(self, *kids): class AliasedExpr(Nonterm): + val: qlast.AliasedExpr + def reduce_Identifier_ASSIGN_Expr(self, *kids): self.val = qlast.AliasedExpr(alias=kids[0].val, expr=kids[2].val) +# NOTE: This is intentionally not an AST node, since this structure never +# makes it to the actual AST and exists solely for parser convenience. +AliasedExprSpec = collections.namedtuple( + 'AliasedExprSpec', ['alias', 'expr'], module=__name__) + + class OptionallyAliasedExpr(Nonterm): + val: AliasedExprSpec + def reduce_AliasedExpr(self, *kids): val = kids[0].val self.val = AliasedExprSpec(alias=val.alias, expr=val.expr) @@ -101,16 +115,12 @@ def reduce_Expr(self, *kids): class AliasedExprList(ListNonterm, element=AliasedExpr, separator=tokens.T_COMMA, allow_trailing_separator=True): - pass - - -# NOTE: This is intentionally not an AST node, since this structure never -# makes it to the actual AST and exists solely for parser convenience. -AliasedExprSpec = collections.namedtuple( - 'AliasedExprSpec', ['alias', 'expr'], module=__name__) + val: typing.List[qlast.AliasedExpr] class GroupingIdent(Nonterm): + val: qlast.GroupingAtom + def reduce_Identifier(self, *kids): self.val = qlast.ObjectRef(name=kids[0].val) @@ -135,10 +145,12 @@ def reduce_AT_Identifier(self, *kids): class GroupingIdentList(ListNonterm, element=GroupingIdent, separator=tokens.T_COMMA): - pass + val: typing.List[qlast.GroupingAtom] class GroupingAtom(Nonterm): + val: qlast.GroupingAtom + @parsing.inline(0) def reduce_GroupingIdent(self, *kids): pass @@ -150,10 +162,12 @@ def reduce_LPAREN_GroupingIdentList_RPAREN(self, *kids): class GroupingAtomList( ListNonterm, element=GroupingAtom, separator=tokens.T_COMMA, allow_trailing_separator=True): - pass + val: typing.List[qlast.GroupingAtom] class GroupingElement(Nonterm): + val: qlast.GroupingElement + def reduce_GroupingAtom(self, *kids): self.val = qlast.GroupingSimple(element=kids[0].val) @@ -170,10 +184,12 @@ def reduce_CUBE_LPAREN_GroupingAtomList_RPAREN(self, *kids): class GroupingElementList( ListNonterm, element=GroupingElement, separator=tokens.T_COMMA, allow_trailing_separator=True): - pass + val: typing.List[qlast.GroupingElement] class OptionalOptional(Nonterm): + val: bool + def reduce_OPTIONAL(self, *kids): self.val = True @@ -182,6 +198,8 @@ def reduce_empty(self, *kids): class SimpleFor(Nonterm): + val: qlast.ForQuery + def reduce_ForIn(self, *kids): r"%reduce FOR OptionalOptional Identifier IN AtomicExpr UNION Expr" _, optional, iterator_alias, _, iterator, _, body = kids @@ -205,6 +223,8 @@ def reduce_ForInStmt(self, *kids): class SimpleSelect(Nonterm): + val: qlast.SelectQuery + def reduce_Select(self, *kids): r"%reduce SELECT OptionallyAliasedExpr \ OptFilterClause OptSortClause OptSelectLimit" @@ -235,18 +255,24 @@ def reduce_Select(self, *kids): class ByClause(Nonterm): + val: typing.List[qlast.GroupingElement] + @parsing.inline(1) def reduce_BY_GroupingElementList(self, *kids): pass class UsingClause(Nonterm): + val: typing.List[qlast.AliasedExpr] + @parsing.inline(1) def reduce_USING_AliasedExprList(self, *kids): pass class OptUsingClause(Nonterm): + val: typing.List[qlast.AliasedExpr] + @parsing.inline(0) def reduce_UsingClause(self, *kids): pass @@ -256,6 +282,8 @@ def reduce_empty(self, *kids): class SimpleGroup(Nonterm): + val: qlast.GroupQuery + def reduce_Group(self, *kids): r"%reduce GROUP OptionallyAliasedExpr \ OptUsingClause \ @@ -269,6 +297,8 @@ def reduce_Group(self, *kids): class OptGroupingAlias(Nonterm): + val: typing.Optional[qlast.GroupQuery] + @parsing.inline(1) def reduce_COMMA_Identifier(self, *kids): pass @@ -278,6 +308,8 @@ def reduce_empty(self, *kids): class InternalGroup(Nonterm): + val: qlast.InternalGroupQuery + def reduce_InternalGroup(self, *kids): r"%reduce FOR GROUP OptionallyAliasedExpr \ UsingClause \ @@ -301,6 +333,8 @@ def reduce_InternalGroup(self, *kids): class SimpleInsert(Nonterm): + val: qlast.InsertQuery + def reduce_Insert(self, *kids): r'%reduce INSERT Expr OptUnlessConflictClause' @@ -356,6 +390,8 @@ def reduce_Insert(self, *kids): class SimpleUpdate(Nonterm): + val: qlast.UpdateQuery + def reduce_Update(self, *kids): "%reduce UPDATE Expr OptFilterClause SET Shape" self.val = qlast.UpdateQuery( @@ -366,6 +402,8 @@ def reduce_Update(self, *kids): class SimpleDelete(Nonterm): + val: qlast.DeleteQuery + def reduce_Delete(self, *kids): r"%reduce DELETE Expr \ OptFilterClause OptSortClause OptSelectLimit" @@ -1845,6 +1883,8 @@ def reduce_empty(self, *kids): class Identifier(Nonterm): + val: str # == Token.value + def reduce_IDENT(self, ident): self.val = ident.clean_value diff --git a/edb/edgeql/parser/grammar/session.py b/edb/edgeql/parser/grammar/session.py index ca55a48cf01..d48cf8e4abd 100644 --- a/edb/edgeql/parser/grammar/session.py +++ b/edb/edgeql/parser/grammar/session.py @@ -28,6 +28,8 @@ class SessionStmt(Nonterm): + val: qlast.Command + @parsing.inline(0) def reduce_SetStmt(self, *kids): pass @@ -38,6 +40,8 @@ def reduce_ResetStmt(self, *kids): class SetStmt(Nonterm): + val: qlast.SessionSetAliasDecl + def reduce_SET_ALIAS_Identifier_AS_MODULE_ModuleName(self, *kids): _, _, alias, _, _, module = kids self.val = qlast.SessionSetAliasDecl( @@ -54,6 +58,8 @@ def reduce_SET_MODULE_ModuleName(self, *kids): class ResetStmt(Nonterm): + val: qlast.SessionResetAliasDecl + def reduce_RESET_ALIAS_Identifier(self, *kids): self.val = qlast.SessionResetAliasDecl( alias=kids[2].val) diff --git a/edb/edgeql/parser/grammar/start.py b/edb/edgeql/parser/grammar/start.py index 859f5cf0225..e0864cdbeda 100644 --- a/edb/edgeql/parser/grammar/start.py +++ b/edb/edgeql/parser/grammar/start.py @@ -18,6 +18,7 @@ from __future__ import annotations +import typing from edb.common import parsing from edb.edgeql import ast as qlast @@ -43,6 +44,7 @@ # in parser.rs `fn get_token_kind` class EdgeQLGrammar(Nonterm): "%start" + val: qlast.GrammarEntryPoint def reduce_STARTBLOCK_EdgeQLBlock_EOI(self, *kids): self.val = kids[1].val @@ -64,6 +66,8 @@ def reduce_STARTSDLDOCUMENT_SDLDocument_EOI(self, *kids): class EdgeQLBlock(Nonterm): + val: typing.List[qlast.Command] + @parsing.inline(0) def reduce_StatementBlock_OptSemicolons(self, _, _semicolon): pass @@ -73,6 +77,8 @@ def reduce_OptSemicolons(self, _semicolon): class SingleStatement(Nonterm): + val: qlast.Command + @parsing.inline(0) def reduce_Stmt(self, _): # Expressions diff --git a/edb/edgeql/parser/grammar/statements.py b/edb/edgeql/parser/grammar/statements.py index 0dfe6511a0f..dd3cbef702e 100644 --- a/edb/edgeql/parser/grammar/statements.py +++ b/edb/edgeql/parser/grammar/statements.py @@ -35,6 +35,7 @@ class Stmt(Nonterm): + val: qlast.Command @parsing.inline(0) def reduce_TransactionStmt(self, stmt): @@ -155,6 +156,7 @@ class DescribeFmt(typing.NamedTuple): class DescribeFormat(Nonterm): + val: DescribeFmt def reduce_empty(self, *kids): self.val = DescribeFmt( @@ -197,6 +199,7 @@ def reduce_AS_TEXT_VERBOSE(self, *kids): class DescribeStmt(Nonterm): + val: qlast.DescribeStmt def reduce_DESCRIBE_SCHEMA(self, *kids): """%reduce DESCRIBE SCHEMA DescribeFormat""" @@ -281,6 +284,7 @@ def reduce_DESCRIBE_CURRENT_MIGRATION(self, *kids): class AnalyzeStmt(Nonterm): + val: qlast.ExplainStmt def reduce_ANALYZE_NamedTuple_ExprStmt(self, *kids): _, args, stmt = kids @@ -297,6 +301,7 @@ def reduce_ANALYZE_ExprStmt(self, *kids): class AdministerStmt(Nonterm): + val: qlast.AdministerStmt def reduce_ADMINISTER_FuncExpr(self, *kids): _, expr = kids diff --git a/edb/edgeql/tracer.py b/edb/edgeql/tracer.py index 3e11edb37bc..b0a7963a75a 100644 --- a/edb/edgeql/tracer.py +++ b/edb/edgeql/tracer.py @@ -1139,6 +1139,7 @@ def trace_GroupingAtom(node: qlast.GroupingAtom, *, ctx: TracerContext) -> None: elif isinstance(node, qlast.Path): trace(node, ctx=ctx) else: + assert isinstance(node, qlast.GroupingIdentList) for el in node.elements: trace_GroupingAtom(el, ctx=ctx) @@ -1167,6 +1168,19 @@ def trace_GroupingOperation( @trace.register def trace_Group( node: qlast.GroupQuery, *, ctx: TracerContext +) -> Optional[ObjectLike]: + return _trace_GroupQuery(node, ctx=ctx) + + +@trace.register +def trace_InternalGroupQuery( + node: qlast.InternalGroupQuery, *, ctx: TracerContext +) -> Optional[ObjectLike]: + return _trace_GroupQuery(node, ctx=ctx) + + +def _trace_GroupQuery( + node: qlast.GroupQuery | qlast.InternalGroupQuery, *, ctx: TracerContext ) -> Optional[ObjectLike]: with alias_context(ctx, node.aliases) as ctx: tip = trace(node.subject, ctx=ctx) diff --git a/edb/schema/annos.py b/edb/schema/annos.py index fe3e74f5324..d5599673cc7 100644 --- a/edb/schema/annos.py +++ b/edb/schema/annos.py @@ -317,7 +317,7 @@ def _deparse_name( def _classname_from_ast( cls, schema: s_schema.Schema, - astnode: qlast.NamedDDL, + astnode: qlast.ObjectDDL, context: sd.CommandContext, ) -> sn.QualName: parent_ctx = cls.get_referrer_context_or_die(context) diff --git a/edb/schema/casts.py b/edb/schema/casts.py index 09f827db3a8..6351dfb0bc2 100644 --- a/edb/schema/casts.py +++ b/edb/schema/casts.py @@ -279,7 +279,7 @@ def _cmd_tree_from_ast( def _classname_from_ast( cls, schema: s_schema.Schema, - astnode: qlast.NamedDDL, + astnode: qlast.ObjectDDL, context: sd.CommandContext, ) -> sn.QualName: assert isinstance(astnode, qlast.CastCommand) diff --git a/edb/schema/constraints.py b/edb/schema/constraints.py index e863b9c0852..ffc921c0be7 100644 --- a/edb/schema/constraints.py +++ b/edb/schema/constraints.py @@ -436,7 +436,7 @@ def _validate_subcommands( def _classname_quals_from_ast( cls, schema: s_schema.Schema, - astnode: qlast.NamedDDL, + astnode: qlast.ObjectDDL, base_name: sn.Name, referrer_name: sn.QualName, context: sd.CommandContext, @@ -473,7 +473,7 @@ def _classname_quals_from_name(cls, name: sn.QualName) -> Tuple[str, ...]: def _constraint_args_from_ast( cls, schema: s_schema.Schema, - astnode: qlast.NamedDDL, + astnode: qlast.ObjectDDL, context: sd.CommandContext, ) -> List[s_expr.Expression]: args = [] @@ -1377,7 +1377,7 @@ class RenameConstraint( def _classname_quals_from_ast( cls, schema: s_schema.Schema, - astnode: qlast.NamedDDL, + astnode: qlast.ObjectDDL, base_name: sn.Name, referrer_name: sn.QualName, context: sd.CommandContext, diff --git a/edb/schema/ddl.py b/edb/schema/ddl.py index 080bc1f6aa6..284b880e622 100644 --- a/edb/schema/ddl.py +++ b/edb/schema/ddl.py @@ -469,14 +469,14 @@ def apply_sdl( testmode: bool = False, ) -> tuple[s_schema.Schema, list[errors.EdgeDBError]]: # group declarations by module - documents: Dict[str, List[qlast.DDL]] = defaultdict(list) + documents: Dict[str, List[qlast.DDLCommand]] = defaultdict(list) # initialize the "default" module documents[s_mod.DEFAULT_MODULE_ALIAS] = [] extensions = {} futures = {} def collect( - decl: qlast.NamedDDL | qlast.ModuleDeclaration, + decl: qlast.ObjectDDL | qlast.ModuleDeclaration, module: Optional[str], ) -> None: # declarations are either in a module block or fully-qualified @@ -494,7 +494,7 @@ def collect( assert not module futures[decl.name.name] = decl else: - assert isinstance(decl, qlast.NamedDDL) + assert isinstance(decl, qlast.ObjectDDL) assert module or decl.name.module is not None if decl.name.module is None: assert module diff --git a/edb/schema/delta.py b/edb/schema/delta.py index 220515671f0..9f00b383237 100644 --- a/edb/schema/delta.py +++ b/edb/schema/delta.py @@ -1826,7 +1826,7 @@ def __init_subclass__(cls, *args: Any, **kwargs: Any) -> None: def _classname_from_ast( cls, schema: s_schema.Schema, - astnode: qlast.NamedDDL, + astnode: qlast.ObjectDDL, context: CommandContext, ) -> sn.Name: return sn.UnqualName(astnode.name.name) @@ -2944,7 +2944,7 @@ class QualifiedObjectCommand(ObjectCommand[so.QualifiedObject_T]): def _classname_from_ast( cls, schema: s_schema.Schema, - astnode: qlast.NamedDDL, + astnode: qlast.ObjectDDL, context: CommandContext, ) -> sn.QualName: objref = astnode.name diff --git a/edb/schema/expr.py b/edb/schema/expr.py index 2db811cb174..d74389cff93 100644 --- a/edb/schema/expr.py +++ b/edb/schema/expr.py @@ -611,6 +611,7 @@ def imprint_expr_context( qltree = copy.copy(qltree) qltree.aliases = ( list(qltree.aliases) if qltree.aliases is not None else None) + assert isinstance(qltree, (qlast_.Query, qlast_.Command)) existing_aliases: Dict[Optional[str], str] = {} for alias in (qltree.aliases or ()): diff --git a/edb/schema/expraliases.py b/edb/schema/expraliases.py index 40d8a8f765a..10012572be8 100644 --- a/edb/schema/expraliases.py +++ b/edb/schema/expraliases.py @@ -276,7 +276,7 @@ def _is_computable(cls, obj: Alias, schema: s_schema.Schema) -> bool: def _classname_from_ast( cls, schema: s_schema.Schema, - astnode: qlast.NamedDDL, + astnode: qlast.ObjectDDL, context: sd.CommandContext, ) -> sn.QualName: type_name = super()._classname_from_ast(schema, astnode, context) diff --git a/edb/schema/extensions.py b/edb/schema/extensions.py index 413d8e3e2cc..152642d741b 100644 --- a/edb/schema/extensions.py +++ b/edb/schema/extensions.py @@ -212,7 +212,7 @@ class ExtensionPackageCommand( def _classname_from_ast( cls, schema: s_schema.Schema, - astnode: qlast.NamedDDL, + astnode: qlast.ObjectDDL, context: sd.CommandContext ) -> sn.UnqualName: assert isinstance(astnode, qlast.ExtensionPackageCommand) @@ -434,7 +434,7 @@ class ExtensionPackageMigrationCommand( def _classname_from_ast( cls, schema: s_schema.Schema, - astnode: qlast.NamedDDL, + astnode: qlast.ObjectDDL, context: sd.CommandContext ) -> sn.UnqualName: assert isinstance(astnode, ( diff --git a/edb/schema/functions.py b/edb/schema/functions.py index a8792324b57..b17fb4b95e3 100644 --- a/edb/schema/functions.py +++ b/edb/schema/functions.py @@ -1503,10 +1503,10 @@ class FunctionCommand( def _classname_from_ast( cls, schema: s_schema.Schema, - astnode: qlast.NamedDDL, + astnode: qlast.ObjectDDL, context: sd.CommandContext, ) -> sn.QualName: - # _classname_from_ast signature expects qlast.NamedDDL, + # _classname_from_ast signature expects qlast.ObjectDDL, # but _get_param_desc_from_ast expects a ObjectDDL, # which is more specific assert isinstance(astnode, qlast.ObjectDDL) @@ -2074,7 +2074,7 @@ class RenameFunction(RenameCallableObject[Function], FunctionCommand): def _classname_from_ast( cls, schema: s_schema.Schema, - astnode: qlast.NamedDDL, + astnode: qlast.ObjectDDL, context: sd.CommandContext, ) -> sn.QualName: ctx = context.current() diff --git a/edb/schema/indexes.py b/edb/schema/indexes.py index 31b6f0a4746..bbcc21e07c3 100644 --- a/edb/schema/indexes.py +++ b/edb/schema/indexes.py @@ -559,7 +559,7 @@ class IndexCommand( def _classname_from_ast( cls, schema: s_schema.Schema, - astnode: qlast.NamedDDL, + astnode: qlast.ObjectDDL, context: sd.CommandContext, ) -> sn.QualName: # We actually want to override how ReferencedObjectCommand determines @@ -593,7 +593,7 @@ def _classname_from_ast( def _classname_quals_from_ast( cls, schema: s_schema.Schema, - astnode: qlast.NamedDDL, + astnode: qlast.ObjectDDL, base_name: sn.Name, referrer_name: sn.QualName, context: sd.CommandContext, @@ -628,7 +628,7 @@ def _classname_quals_from_name(cls, name: sn.QualName) -> Tuple[str, ...]: def _index_kwargs_from_ast( cls, schema: s_schema.Schema, - astnode: qlast.NamedDDL, + astnode: qlast.ObjectDDL, context: sd.CommandContext, ) -> Dict[str, s_expr.Expression]: kwargs = dict() @@ -1816,7 +1816,7 @@ def _cmd_tree_from_ast( def _classname_from_ast( cls, schema: s_schema.Schema, - astnode: qlast.NamedDDL, + astnode: qlast.ObjectDDL, context: sd.CommandContext, ) -> sn.QualName: assert isinstance(astnode, qlast.IndexMatchCommand) diff --git a/edb/schema/operators.py b/edb/schema/operators.py index 85622599749..c62c7f5cf15 100644 --- a/edb/schema/operators.py +++ b/edb/schema/operators.py @@ -154,7 +154,7 @@ def _cmd_tree_from_ast( def _classname_from_ast( cls, schema: s_schema.Schema, - astnode: qlast.NamedDDL, + astnode: qlast.ObjectDDL, context: sd.CommandContext, ) -> sn.QualName: assert isinstance(astnode, qlast.OperatorCommand) diff --git a/edb/schema/referencing.py b/edb/schema/referencing.py index 99dd2e65dea..acccb88c6e3 100644 --- a/edb/schema/referencing.py +++ b/edb/schema/referencing.py @@ -477,7 +477,7 @@ def _classname_from_ast_and_referrer( cls, schema: s_schema.Schema, referrer_name: sn.QualName, - astnode: qlast.NamedDDL, + astnode: qlast.ObjectDDL, context: sd.CommandContext ) -> sn.QualName: base_ref = utils.ast_to_object_shell( @@ -497,7 +497,7 @@ def _classname_from_ast_and_referrer( def _classname_from_ast( cls, schema: s_schema.Schema, - astnode: qlast.NamedDDL, + astnode: qlast.ObjectDDL, context: sd.CommandContext, ) -> sn.QualName: parent_ctx = cls.get_referrer_context(context) @@ -528,7 +528,7 @@ def _classname_from_name( def _classname_quals_from_ast( cls, schema: s_schema.Schema, - astnode: qlast.NamedDDL, + astnode: qlast.ObjectDDL, base_name: sn.Name, referrer_name: sn.QualName, context: sd.CommandContext, @@ -1655,7 +1655,7 @@ class NamedReferencedInheritingObjectCommand( def _classname_from_ast( cls, schema: s_schema.Schema, - astnode: qlast.NamedDDL, + astnode: qlast.ObjectDDL, context: sd.CommandContext, ) -> sn.QualName: referrer_ctx = cls.get_referrer_context(context) diff --git a/edb/server/compiler/status.py b/edb/server/compiler/status.py index 9c11af6e33d..d6ed5cf8071 100644 --- a/edb/server/compiler/status.py +++ b/edb/server/compiler/status.py @@ -72,6 +72,8 @@ def get_schema_class(ql: qlast.ObjectDDL) -> qltypes.SchemaObjectClass: return osc.LINK case qlast.IndexCommand(): return osc.INDEX + case qlast.AccessPolicyCommand(): + return osc.INDEX_MATCH case qlast.TriggerCommand(): return osc.TRIGGER case qlast.RewriteCommand(): @@ -174,6 +176,7 @@ def _ddl_migr_reset_schema(ql: qlast.Base) -> bytes: @get_status.register(qlast.SelectQuery) @get_status.register(qlast.GroupQuery) +@get_status.register(qlast.InternalGroupQuery) @get_status.register(qlast.ForQuery) def _select(ql: qlast.Base) -> bytes: return b'SELECT' diff --git a/edb/testbase/lang.py b/edb/testbase/lang.py index 24291c1e4ac..5e9f3453a7c 100644 --- a/edb/testbase/lang.py +++ b/edb/testbase/lang.py @@ -410,7 +410,7 @@ def run_ddl(cls, schema, ddl, default_module=s_mod.DEFAULT_MODULE_ALIAS): migration_target = None migration_script = [] - elif isinstance(stmt, qlast.DDL): + elif isinstance(stmt, qlast.DDLCommand): if migration_target is not None: migration_script.append(stmt) ddl_plan = None diff --git a/edb/tools/ast_inheritance_graph.py b/edb/tools/ast_inheritance_graph.py index 1142ecb5875..2c33086708f 100644 --- a/edb/tools/ast_inheritance_graph.py +++ b/edb/tools/ast_inheritance_graph.py @@ -28,13 +28,13 @@ class ASTModule(str, enum.Enum): class ASTClass: name: str typ: typing.Type + bases: typing.Set[str] + children: typing.Set[str] @edbcommands.command("ast-inheritance-graph") @click.argument('ast', type=click.Choice(ASTModule)) # type: ignore def main(ast: ASTModule) -> None: - print('digraph G {') - ast_mod: typing.Any if ast == ASTModule.ql: ast_mod = qlast @@ -57,22 +57,56 @@ def main(ast: ASTModule) -> None: }: continue - if typ.__abstract_node__: # type: ignore - print(f' {name} [color = red];') - if typ.__rust_ignore__: # type: ignore continue # re-run field collection to correctly handle forward-references typ = typ._collect_direct_fields() # type: ignore - ast_classes[typ.__name__] = ASTClass(name=name, typ=typ) + ast_classes[typ.__name__] = ASTClass( + name=name, + typ=typ, + children=set(), + bases=set(), + ) - # build inheritance graph for ast_class in ast_classes.values(): for base in ast_class.typ.__bases__: if base.__name__ not in ast_classes: continue - print(f' {ast_class.name} -> {base.__name__};') + ast_class.bases.add(base.__name__) + ast_classes[base.__name__].children.add(ast_class.name) + + inheritance_graph(ast_classes) + enum_graph(ast_classes) + + +def inheritance_graph(ast_classes: typing.Dict[str, ASTClass]): + print('digraph I {') + for ast_class in ast_classes.values(): + if ast_class.typ.__abstract_node__: + print(f' {ast_class.name} [color = red];') + for base in ast_class.bases: + print(f' {ast_class.name} -> {base};') + + print('}') + + +def enum_graph(ast_classes: typing.Dict[str, ASTClass]): + print('digraph M {') + + def dfs(node, start): + ast_class = ast_classes[node] + if ast_class.typ.__abstract_node__: + print(f' {node}_{start} [color = red];') + + for child in ast_class.children: + print(f' {node}_{start} -> {child}_{start};') + dfs(child, start) + + for ast_class in ast_classes.values(): + if len(ast_class.bases) != 0 or len(ast_class.children) == 0: + continue + dfs(ast_class.name, ast_class.name) print('}') diff --git a/edb/tools/toy_eval_model.py b/edb/tools/toy_eval_model.py index c5bb748693f..9bdea909674 100644 --- a/edb/tools/toy_eval_model.py +++ b/edb/tools/toy_eval_model.py @@ -673,13 +673,16 @@ def flatten_grouping_atom(atom: qlast.GroupingAtom) -> Tuple[ByElement, ...]: if isinstance(atom, (qlast.ObjectRef, qlast.Path)): return (get_by_element(atom),) else: + assert isinstance(atom, qlast.GroupingIdentList) return tuple( x for g in atom.elements for x in flatten_grouping_atom(g) ) -def get_grouping_sets(node: qlast.GroupQuery) -> List[Tuple[ByElement, ...]]: +def get_grouping_sets( + node: qlast.GroupQuery | qlast.InternalGroupQuery +) -> List[Tuple[ByElement, ...]]: toplevel_gsets = [] for col in node.by: gsets = simplify_grouping_sets(col) @@ -703,7 +706,7 @@ def _keyify(v: Data) -> Data: def get_groups( - node: qlast.GroupQuery, ctx: EvalContext + node: qlast.GroupQuery | qlast.InternalGroupQuery, ctx: EvalContext ) -> List[Tuple[Tuple[Data, ...], Tuple[Dict[ByElement, Data], List[Data]]]]: ctx = eval_aliases(node, ctx)