From ba1baed5bb7d3271144a4ffe52c2a28499039d7a Mon Sep 17 00:00:00 2001 From: Devon Campbell Date: Mon, 25 Mar 2024 12:13:29 -0400 Subject: [PATCH 1/8] Doc remove glossary (#7104) * Remove glossary page It's currently hidden but shows up in search, generating a 404 * Fix typo * Remove link into glossary --- docs/glossary.rst | 31 ------------------------------- docs/reference/admin/branches.rst | 2 +- docs/reference/edgeql/eval.rst | 8 ++++---- 3 files changed, 5 insertions(+), 36 deletions(-) delete mode 100644 docs/glossary.rst diff --git a/docs/glossary.rst b/docs/glossary.rst deleted file mode 100644 index cf21c270770..00000000000 --- a/docs/glossary.rst +++ /dev/null @@ -1,31 +0,0 @@ -:orphan: - -.. _glossary: - -======== -Glossary -======== - -.. NOTE: Please keep the entries sorted alphabetically - -.. glossary:: - - DDL - Data Definition Language. DDL is a type of database-specific - syntax used to define the structuring of schemas. Common DDL - statements include ``create``, ``drop``, and ``alter``. - - link - Link items define a specific relationship between two object types. Link - instances relate one object to one or more different objects. - - More on links in :ref:`Data Model `. - - set reference - An identifier that represents a set of values. It can be the name of an - object type or an *expression alias* (defined in a statement :ref:`with - block ` or in the schema via an :ref:`alias - declaration `. or a qualified schema name). - - simple path - A path which begins with a :term:`set reference`. diff --git a/docs/reference/admin/branches.rst b/docs/reference/admin/branches.rst index aef50e06fde..95b3b4eb6fb 100644 --- a/docs/reference/admin/branches.rst +++ b/docs/reference/admin/branches.rst @@ -10,7 +10,7 @@ Branch This section describes the administrative commands pertaining to -:ref:`branchs `. +:ref:`branches `. Create empty branch diff --git a/docs/reference/edgeql/eval.rst b/docs/reference/edgeql/eval.rst index c7339580984..29ba9fef06b 100644 --- a/docs/reference/edgeql/eval.rst +++ b/docs/reference/edgeql/eval.rst @@ -29,10 +29,10 @@ A nested query is called a *subquery*. Here, the phrase A query is evaluated recursively using the following procedure: -1. Make a list of :term:`simple paths ` appearing directly the - query. For every path in the list, find all paths which begin with the - same set reference and treat their longest common prefix as an equivalent - set reference. +1. Make a list of simple paths (i.e., paths that begin with a set reference) + appearing directly the query. For every path in the list, find all paths + which begin with the same set reference and treat their longest common + prefix as an equivalent set reference. Example: From 72fcd339bd250acd60eaf5d5444c40292a736362 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alja=C5=BE=20Mur=20Er=C5=BEen?= Date: Mon, 25 Mar 2024 19:46:45 +0100 Subject: [PATCH 2/8] Refactor irast.Set.rptr into Set.expr (part 1 / n) (#7111) Followup for https://github.com/edgedb/edgedb/pull/7090 --- edb/edgeql/compiler/config.py | 6 +- edb/edgeql/compiler/conflicts.py | 18 ++-- edb/edgeql/compiler/eta_expand.py | 8 +- edb/edgeql/compiler/group.py | 15 +-- edb/edgeql/compiler/inference/cardinality.py | 96 +++++++++---------- edb/edgeql/compiler/inference/multiplicity.py | 25 ++--- edb/edgeql/compiler/inference/volatility.py | 12 +-- edb/edgeql/compiler/setgen.py | 52 +++++----- edb/edgeql/compiler/stmt.py | 66 ++++++------- edb/edgeql/compiler/typegen.py | 6 +- edb/edgeql/compiler/viewgen.py | 38 +++++--- edb/ir/utils.py | 48 +++++++--- edb/pgsql/compiler/astutils.py | 10 +- edb/pgsql/compiler/expr.py | 10 +- edb/pgsql/compiler/shapecomp.py | 4 +- edb/schema/indexes.py | 21 +--- edb/server/compiler/ddl.py | 8 +- 17 files changed, 235 insertions(+), 208 deletions(-) diff --git a/edb/edgeql/compiler/config.py b/edb/edgeql/compiler/config.py index da4577eebdf..47d8775fc2e 100644 --- a/edb/edgeql/compiler/config.py +++ b/edb/edgeql/compiler/config.py @@ -269,12 +269,12 @@ def _validate_config_object( ctx: context.ContextLevel) -> None: for element, _ in expr.shape: - assert element.rptr is not None - if element.rptr.ptrref.shortname.name == 'id': + assert isinstance(element.expr, irast.Pointer) + if element.expr.ptrref.shortname.name == 'id': continue ptr = typegen.ptrcls_from_ptrref( - element.rptr.ptrref.real_material_ptr, + element.expr.ptrref.real_material_ptr, ctx=ctx, ) if isinstance(ptr, s_pointers.Pointer): diff --git a/edb/edgeql/compiler/conflicts.py b/edb/edgeql/compiler/conflicts.py index 87fa34b4a56..63dd77c7c45 100644 --- a/edb/edgeql/compiler/conflicts.py +++ b/edb/edgeql/compiler/conflicts.py @@ -494,20 +494,20 @@ def compile_insert_unless_conflict_on( # We accept a property, link, or a list of them in the form of a # tuple. - if cspec_res.rptr is None and isinstance(cspec_res.expr, irast.Tuple): + if isinstance(cspec_res.expr, irast.Tuple): cspec_args = [elem.val for elem in cspec_res.expr.elements] else: cspec_args = [cspec_res] for cspec_arg in cspec_args: - if not cspec_arg.rptr: + if not isinstance(cspec_arg.expr, irast.Pointer): raise errors.QueryError( 'UNLESS CONFLICT argument must be a property, link, ' 'or tuple of properties and links', span=constraint_spec.span, ) - if cspec_arg.rptr.source.path_id != stmt.subject.path_id: + if cspec_arg.expr.source.path_id != stmt.subject.path_id: raise errors.QueryError( 'UNLESS CONFLICT argument must be a property of the ' 'type being inserted', @@ -519,9 +519,9 @@ def compile_insert_unless_conflict_on( ptrs = [] exclusive_constr = schema.get('std::exclusive', type=s_constr.Constraint) for cspec_arg in cspec_args: - assert cspec_arg.rptr is not None + assert isinstance(cspec_arg.expr, irast.Pointer) schema, ptr = ( - typeutils.ptrcls_from_ptrref(cspec_arg.rptr.ptrref, schema=schema)) + typeutils.ptrcls_from_ptrref(cspec_arg.expr.ptrref, schema=schema)) if not isinstance(ptr, s_pointers.Pointer): raise errors.QueryError( 'UNLESS CONFLICT argument must be a property, link, ' @@ -593,8 +593,8 @@ def compile_insert_unless_conflict_on( def _has_explicit_id_write(stmt: irast.MutatingStmt) -> bool: for elem, _ in stmt.subject.shape: - assert elem.rptr is not None - if elem.rptr.ptrref.shortname.name == 'id': + assert isinstance(elem.expr, irast.Pointer) + if elem.expr.ptrref.shortname.name == 'id': return elem.span is not None return False @@ -656,9 +656,9 @@ def _compile_inheritance_conflict_selects( shape_ptrs = set() for elem, op in stmt.subject.shape: - assert elem.rptr is not None + assert isinstance(elem.expr, irast.Pointer) if op != qlast.ShapeOp.MATERIALIZE: - shape_ptrs.add(elem.rptr.ptrref.shortname.name) + shape_ptrs.add(elem.expr.ptrref.shortname.name) # This is a little silly, but for *this* we need to do one per # constraint (so that we can properly identify which constraint diff --git a/edb/edgeql/compiler/eta_expand.py b/edb/edgeql/compiler/eta_expand.py index 2be88038fcd..a2f7474af7b 100644 --- a/edb/edgeql/compiler/eta_expand.py +++ b/edb/edgeql/compiler/eta_expand.py @@ -179,11 +179,11 @@ def needs_eta_expansion( # an important optimization to support, but our expansion can generate # this idiom, so on principle I wanted to support it. if ( - isinstance(ir.rptr, irast.TupleIndirectionPointer) - and isinstance(ir.rptr.source.expr, irast.Tuple) + isinstance(ir.expr, irast.TupleIndirectionPointer) + and isinstance(ir.expr.source.expr, irast.Tuple) ): - name = ir.rptr.ptrref.shortname.name - els = [x for x in ir.rptr.source.expr.elements if x.name == name] + name = ir.expr.ptrref.shortname.name + els = [x for x in ir.expr.source.expr.elements if x.name == name] if len(els) == 1: return needs_eta_expansion(els[0].val, ctx=ctx) diff --git a/edb/edgeql/compiler/group.py b/edb/edgeql/compiler/group.py index f57a2ceabd2..3bcd6163634 100644 --- a/edb/edgeql/compiler/group.py +++ b/edb/edgeql/compiler/group.py @@ -118,9 +118,9 @@ def visit_Set(self, node: irast.Set, skip_rptr: bool=False) -> None: # because the bodies are executed one at a time, and so the # semi-join deduplication doesn't work. is_semijoin = ( - node.rptr + isinstance(node.expr, irast.Pointer) and node.path_id.is_objtype_path() - and not self.scope_tree.is_visible(node.rptr.source.path_id) + and not self.scope_tree.is_visible(node.expr.source.path_id) ) old = self.aggregate @@ -130,11 +130,12 @@ def visit_Set(self, node: irast.Set, skip_rptr: bool=False) -> None: self.visit(node.shape) # XXX: old_expr - if not node.old_expr and node.rptr: - self.visit(node.rptr.source) - elif node.rptr: - if node.rptr.source.path_id not in self.seen: - self.seen[node.rptr.source.path_id] = False + if isinstance(node.expr, irast.Pointer): + if not node.old_expr: + self.visit(node.expr.source) + else: + if node.expr.source.path_id not in self.seen: + self.seen[node.expr.source.path_id] = False if isinstance(node.expr, irast.Call): self.process_call(node.expr, node) diff --git a/edb/edgeql/compiler/inference/cardinality.py b/edb/edgeql/compiler/inference/cardinality.py index 6ef50bad5b8..c91fd14baf1 100644 --- a/edb/edgeql/compiler/inference/cardinality.py +++ b/edb/edgeql/compiler/inference/cardinality.py @@ -584,7 +584,6 @@ def _infer_set_inner( scope_tree: irast.ScopeTreeNode, ctx: inference_context.InfCtx, ) -> qltypes.Cardinality: - rptr = ir.rptr new_scope = inf_utils.get_set_scope(ir, scope_tree, ctx=ctx) # TODO: Migrate to Pointer-as-Expr well, and not half-assedly. @@ -592,44 +591,44 @@ def _infer_set_inner( expr_card = infer_cardinality( ir.old_expr, scope_tree=new_scope, ctx=ctx) - if rptr is not None and not rptr.is_phony: - rptrref = rptr.ptrref + if isinstance(ir.expr, irast.Pointer) and not ir.expr.is_phony: + ptr = ir.expr - assert ir is not rptr.source, "self-referential pointer" + assert ir is not ptr.source, "self-referential pointer" # FIXME: The thing blocking extracting Pointer inference from # here is that this source inference relies on using the old # scope_tree. I think this is probably fixable. source_card = infer_cardinality( - rptr.source, scope_tree=scope_tree, ctx=ctx, + ptr.source, scope_tree=scope_tree, ctx=ctx, ) ctx.env.schema, ptrcls = typeutils.ptrcls_from_ptrref( - rptrref, schema=ctx.env.schema) - if rptr.expr: + ptr.ptrref, schema=ctx.env.schema) + if ptr.expr: assert isinstance(ptrcls, s_pointers.Pointer) _infer_pointer_cardinality( ptrcls=ptrcls, - ptrref=rptrref, - irexpr=rptr.expr, + ptrref=ptr.ptrref, + irexpr=ptr.expr, scope_tree=scope_tree, ctx=ctx, ) - if rptrref.union_components: + if ptr.ptrref.union_components: # We use cartesian cardinality instead of union cardinality # because the union of pointers in this context is disjoint # in a sense that for any specific source only a given union # component is used. rptrref_card = cartesian_cardinality( - c.dir_cardinality(rptr.direction) - for c in rptrref.union_components + c.dir_cardinality(ptr.direction) + for c in ptr.ptrref.union_components ) - elif ctx.ignore_computed_cards and rptr.expr: + elif ctx.ignore_computed_cards and ptr.expr: rptrref_card = expr_card - elif isinstance(rptrref, irast.TypeIntersectionPointerRef): + elif isinstance(ptr.ptrref, irast.TypeIntersectionPointerRef): rptrref_card = AT_MOST_ONE else: - rptrref_card = rptrref.dir_cardinality(rptr.direction) + rptrref_card = ptr.ptrref.dir_cardinality(ptr.direction) card = cartesian_cardinality((source_card, rptrref_card)) @@ -851,34 +850,31 @@ def __infer_typecast( def _is_ptr_or_self_ref( - ir_expr: irast.Base, + set: irast.Base, result_expr: irast.Set, env: context.Environment, ) -> bool: - if not isinstance(ir_expr, irast.Set): + if not isinstance(set, irast.Set): return False - else: - ir_set = ir_expr - srccls = env.set_types[result_expr] + srccls = env.set_types[result_expr] + if not isinstance(srccls, s_objtypes.ObjectType): + return False + + if set.path_id == result_expr.path_id: + return True + + if isinstance(set.expr, irast.Pointer): + rptr = set.expr return ( - isinstance(srccls, s_objtypes.ObjectType) - and ( - ir_expr.path_id == result_expr.path_id - or ( - (rptr := ir_set.rptr) is not None - and isinstance(rptr.ptrref, irast.PointerRef) - and not rptr.ptrref.is_computable - and _is_ptr_or_self_ref(rptr.source, result_expr, env) - ) - or ( - ir_set.rptr is None - and irutils.is_implicit_wrapper(ir_set.expr) - and _is_ptr_or_self_ref( - ir_set.expr.result, result_expr, env) - ) - ) + isinstance(rptr.ptrref, irast.PointerRef) + and not rptr.ptrref.is_computable + and _is_ptr_or_self_ref(rptr.source, result_expr, env) ) + elif irutils.is_implicit_wrapper(set.expr): + return _is_ptr_or_self_ref(set.expr.result, result_expr, env) + else: + return False def extract_filters( @@ -915,24 +911,26 @@ def extract_filters( if infer_cardinality( right, scope_tree=scope_tree, ctx=ctx, ).is_single(): - ptrs = [] + pointers = [] left_stype = env.set_types[left] if left_stype == result_stype: assert isinstance(left_stype, s_objtypes.ObjectType) - _ptr = left_stype.getptr(schema, sn.UnqualName('id')) - ptrs.append(_ptr) + ptr = left_stype.getptr(schema, sn.UnqualName('id')) + pointers.append(ptr) else: - if left.rptr is None: - left = irutils.unwrap_set(left) + left = irutils.unwrap_set(left) + while left.path_id != result_set.path_id: - assert left.rptr is not None - _ptr = env.schema.get(left.rptr.ptrref.name, - type=s_pointers.Pointer) - ptrs.append(_ptr) - left = left.rptr.source - ptrs.reverse() - - return [(ptrs, right)] + assert isinstance(left.expr, irast.Pointer) + ptr = env.schema.get( + left.expr.ptrref.name, + type=s_pointers.Pointer + ) + pointers.append(ptr) + left = left.expr.source + pointers.reverse() + + return [(pointers, right)] elif str(expr.func_shortname) == 'std::AND': left, right = (irutils.unwrap_set(a.expr) for a in expr.args) diff --git a/edb/edgeql/compiler/inference/multiplicity.py b/edb/edgeql/compiler/inference/multiplicity.py index e80d8d197e6..a39003502a4 100644 --- a/edb/edgeql/compiler/inference/multiplicity.py +++ b/edb/edgeql/compiler/inference/multiplicity.py @@ -237,7 +237,7 @@ def _infer_set( ctx: inf_ctx.InfCtx, ) -> inf_ctx.MultiplicityInfo: result = _infer_set_inner( - ir, is_mutation=is_mutation, scope_tree=scope_tree, ctx=ctx + ir, scope_tree=scope_tree, ctx=ctx ) ctx.inferred_multiplicity[ir, scope_tree, ctx.distinct_iterator] = result @@ -250,11 +250,9 @@ def _infer_set( def _infer_set_inner( ir: irast.Set, *, - is_mutation: bool=False, scope_tree: irast.ScopeTreeNode, ctx: inf_ctx.InfCtx, ) -> inf_ctx.MultiplicityInfo: - rptr = ir.rptr new_scope = inf_utils.get_set_scope(ir, scope_tree, ctx=ctx) # TODO: Migrate to Pointer-as-Expr well, and not half-assedly. @@ -264,14 +262,15 @@ def _infer_set_inner( expr_mult = infer_multiplicity( ir.old_expr, scope_tree=new_scope, ctx=ctx) - if rptr is not None: - rptrref = rptr.ptrref + if isinstance(ir.expr, irast.Pointer): + ptr = ir.expr src_mult = infer_multiplicity( - rptr.source, scope_tree=new_scope, ctx=ctx) + ptr.source, scope_tree=new_scope, ctx=ctx + ) - if isinstance(rptrref, irast.TupleIndirectionPointerRef): + if isinstance(ptr.ptrref, irast.TupleIndirectionPointerRef): if isinstance(src_mult, ContainerMultiplicityInfo): - idx = irtyputils.get_tuple_element_index(rptrref) + idx = irtyputils.get_tuple_element_index(ptr.ptrref) path_mult = src_mult.elements[idx] else: # All bets are off for tuple elements coming from @@ -284,16 +283,18 @@ def _infer_set_inner( # unless we also have an exclusive constraint. if ( expr_mult is not None - and inf_utils.find_visible(rptr.source, new_scope) is not None + and inf_utils.find_visible(ptr.source, new_scope) is not None ): path_mult = expr_mult else: schema = ctx.env.schema # We should only have some kind of path terminating in a # property here. - assert isinstance(rptrref, irast.PointerRef) - ptr = schema.get_by_id(rptrref.id, type=s_pointers.Pointer) - if ptr.is_exclusive(schema): + assert isinstance(ptr.ptrref, irast.PointerRef) + pointer = schema.get_by_id( + ptr.ptrref.id, type=s_pointers.Pointer + ) + if pointer.is_exclusive(schema): # Got an exclusive constraint path_mult = UNIQUE else: diff --git a/edb/edgeql/compiler/inference/volatility.py b/edb/edgeql/compiler/inference/volatility.py index 372715a0315..d85a2aa9772 100644 --- a/edb/edgeql/compiler/inference/volatility.py +++ b/edb/edgeql/compiler/inference/volatility.py @@ -162,15 +162,15 @@ def __infer_set( # TODO: Migrate to Pointer-as-Expr a little less half-assedly. if ir.path_id in env.singletons: vol = IMMUTABLE - elif ir.rptr is not None: - vol = _infer_volatility(ir.rptr.source, env) + elif isinstance(ir.expr, irast.Pointer): + vol = _infer_volatility(ir.expr.source, env) # If there's an expression on an rptr, and it comes from # the schema, we need to actually infer it, since it won't # have been processed at a shape declaration. - if ir.rptr.expr is not None and not ir.rptr.ptrref.defined_here: + if ir.expr.expr is not None and not ir.expr.ptrref.defined_here: vol = _max_volatility(( vol, - _infer_volatility(ir.rptr.expr, env), + _infer_volatility(ir.expr.expr, env), )) # If source is an object, then a pointer reference implies @@ -180,8 +180,8 @@ def __infer_set( # though, which we need in order to enforce that indexes # don't call STABLE functions. if ( - irtyputils.is_object(ir.rptr.source.typeref) - and ir.rptr.source.path_id not in env.singletons + irtyputils.is_object(ir.expr.source.typeref) + and ir.expr.source.path_id not in env.singletons ): vol = _max_volatility((vol, STABLE)) elif ir.expr is not None: diff --git a/edb/edgeql/compiler/setgen.py b/edb/edgeql/compiler/setgen.py index 2bd947065a6..1edd7d61db9 100644 --- a/edb/edgeql/compiler/setgen.py +++ b/edb/edgeql/compiler/setgen.py @@ -406,7 +406,7 @@ def compile_path(expr: qlast.Path, *, ctx: context.ContextLevel) -> irast.Set: if ptr_expr.type == 'property': # Link property reference; the source is the # link immediately preceding this step in the path. - if path_tip.rptr is None: + if not isinstance(path_tip.expr, irast.Pointer): raise errors.EdgeQLSyntaxError( f"unexpected reference to link property {ptr_name!r} " "outside of a path expression", @@ -427,22 +427,23 @@ def compile_path(expr: qlast.Path, *, ctx: context.ContextLevel) -> irast.Set: 'constraint definitions', span=step.span) - if isinstance(path_tip.rptr.ptrref, - irast.TypeIntersectionPointerRef): + if isinstance( + path_tip.expr.ptrref, irast.TypeIntersectionPointerRef + ): ind_prefix, ptrs = typegen.collapse_type_intersection_rptr( path_tip, ctx=ctx, ) - assert ind_prefix.rptr is not None - prefix_type = get_set_type(ind_prefix.rptr.source, ctx=ctx) + assert isinstance(ind_prefix.expr, irast.Pointer) + prefix_type = get_set_type(ind_prefix.expr.source, ctx=ctx) assert isinstance(prefix_type, s_objtypes.ObjectType) if not ptrs: tip_type = get_set_type(path_tip, ctx=ctx) s_vn = prefix_type.get_verbosename(ctx.env.schema) t_vn = tip_type.get_verbosename(ctx.env.schema) - pn = ind_prefix.rptr.ptrref.shortname.name + pn = ind_prefix.expr.ptrref.shortname.name if direction is s_pointers.PointerDirection.Inbound: s_vn, t_vn = t_vn, s_vn raise errors.InvalidReferenceError( @@ -458,13 +459,13 @@ def compile_path(expr: qlast.Path, *, ctx: context.ContextLevel) -> irast.Set: ptr = schemactx.get_union_pointer( ptrname=prefix_ptr_name, source=prefix_type, - direction=ind_prefix.rptr.direction, + direction=ind_prefix.expr.direction, components=ptrs, ctx=ctx, ) else: ptr = typegen.ptrcls_from_ptrref( - path_tip.rptr.ptrref, ctx=ctx) + path_tip.expr.ptrref, ctx=ctx) if isinstance(ptr, s_links.Link): source = ptr @@ -503,9 +504,9 @@ def compile_path(expr: qlast.Path, *, ctx: context.ContextLevel) -> irast.Set: ignore_computable=True, span=step.span, ctx=ctx) - assert path_tip.rptr is not None + assert isinstance(path_tip.expr, irast.Pointer) ptrcls = typegen.ptrcls_from_ptrref( - path_tip.rptr.ptrref, ctx=ctx) + path_tip.expr.ptrref, ctx=ctx) if _is_computable_ptr(ptrcls, direction, ctx=ctx): is_computable = True @@ -564,8 +565,8 @@ def compile_path(expr: qlast.Path, *, ctx: context.ContextLevel) -> irast.Set: ctx=subctx) if path_tip.path_id.is_type_intersection_path(): - assert path_tip.rptr is not None - scope_set = path_tip.rptr.source + assert isinstance(path_tip.expr, irast.Pointer) + scope_set = path_tip.expr.source else: scope_set = path_tip @@ -612,13 +613,14 @@ def compile_path(expr: qlast.Path, *, ctx: context.ContextLevel) -> irast.Set: with ctx.new() as subctx: subctx.path_scope = scope - assert ir_set.rptr is not None + assert isinstance(ir_set.expr, irast.Pointer) comp_ir_set = computable_ptr_set( - ir_set.rptr, ir_set.path_id, srcctx=ir_set.span, ctx=subctx) + ir_set.expr, ir_set.path_id, srcctx=ir_set.span, ctx=subctx + ) i = path_sets.index(ir_set) if i != len(path_sets) - 1: - prptr = path_sets[i + 1].rptr - assert prptr is not None + prptr = path_sets[i + 1].expr + assert isinstance(prptr, irast.Pointer) prptr.source = comp_ir_set else: path_tip = comp_ir_set @@ -1239,17 +1241,21 @@ def type_intersection_set( return source_set poly_set = new_set(stype=result.stype, span=span, ctx=ctx) - rptr = source_set.rptr rptr_specialization = [] - if rptr is not None and rptr.ptrref.union_components: + if ( + isinstance(source_set.expr, irast.Pointer) + and source_set.expr.ptrref.union_components + ): + rptr = source_set.expr + # This is a type intersection of a union pointer, most likely # a reverse link path specification. If so, test the union # components against the type expression and record which # components match. This information will be used later # when evaluating the path cardinality, as well as to # route link property references accordingly. - for component in rptr.ptrref.union_components: + for component in source_set.expr.ptrref.union_components: component_endpoint_ref = component.dir_target(rptr.direction) ctx.env.schema, component_endpoint = irtyputils.ir_typeref_to_type( ctx.env.schema, component_endpoint_ref) @@ -1477,12 +1483,12 @@ def fixup_computable_source_set( source_set = new_set_from_set( source_set, stype=source_set_stype, ctx=ctx) source_set.shape = () - # XXX: consistency - if source_set.rptr is not None: - source_rptrref = source_set.rptr.ptrref + + if isinstance(source_set.expr, irast.Pointer): + source_rptrref = source_set.expr.ptrref if source_rptrref.base_ptr is not None: source_rptrref = source_rptrref.base_ptr - source_set.expr = source_set.rptr.replace( + source_set.expr = source_set.expr.replace( ptrref=source_rptrref, is_definition=True, ) diff --git a/edb/edgeql/compiler/stmt.py b/edb/edgeql/compiler/stmt.py index e6135bdde96..11c9295ab6d 100644 --- a/edb/edgeql/compiler/stmt.py +++ b/edb/edgeql/compiler/stmt.py @@ -30,6 +30,7 @@ Sequence, DefaultDict, List, + cast ) from collections import defaultdict @@ -1391,7 +1392,8 @@ def compile_result_clause( def compile_query_subject( - expr: irast.Set, *, + set: irast.Set, + *, shape: Optional[List[qlast.ShapeElement]]=None, view_rptr: Optional[context.ViewRPtr]=None, view_name: Optional[s_name.QualName]=None, @@ -1404,31 +1406,31 @@ def compile_query_subject( span: Optional[qlast.Span], ctx: context.ContextLevel) -> irast.Set: - expr_stype = setgen.get_set_type(expr, ctx=ctx) - expr_rptr = expr.rptr + set_stype = setgen.get_set_type(set, ctx=ctx) - while isinstance(expr_rptr, irast.TypeIntersectionPointer): - expr_rptr = expr_rptr.source.rptr + set_expr = set.expr + while isinstance(set_expr, irast.TypeIntersectionPointer): + set_expr = set_expr.source.expr is_ptr_alias = ( view_rptr is not None and view_rptr.ptrcls is None and view_rptr.ptrcls_name is not None - and expr_rptr is not None - and expr_rptr.source.rptr is None + and isinstance(set_expr, irast.Pointer) + and not isinstance(set_expr.source.expr, irast.Pointer) and ( view_rptr.source.get_bases(ctx.env.schema).first(ctx.env.schema).id - == expr_rptr.source.typeref.id + == set_expr.source.typeref.id ) and ( view_rptr.ptrcls_is_linkprop - == (expr_rptr.ptrref.source_ptr is not None) + == (set_expr.ptrref.source_ptr is not None) ) ) if is_ptr_alias: assert view_rptr is not None - assert expr_rptr is not None + set_rptr = cast(irast.Pointer, set_expr) # We are inside an expression that defines a link alias in # the parent shape, ie. Spam { alias := Spam.bar }, so # `Spam.alias` should be a subclass of `Spam.bar` inheriting @@ -1437,16 +1439,16 @@ def compile_query_subject( # We also try to detect reverse aliases like `. irast.Set: diff --git a/edb/edgeql/compiler/typegen.py b/edb/edgeql/compiler/typegen.py index be94c7cc2aa..63af01d02e1 100644 --- a/edb/edgeql/compiler/typegen.py +++ b/edb/edgeql/compiler/typegen.py @@ -330,10 +330,10 @@ def collapse_type_intersection_rptr( ind_ptr.ptrref.rptr_specialization) elif ( not ind_ptr.ptrref.is_empty - and ind_ptr.source.rptr is not None + and isinstance(ind_ptr.source.expr, irast.Pointer) ): - assert isinstance(ind_ptr.source.rptr.ptrref, irast.PointerRef) - rptr_specialization.add(ind_ptr.source.rptr.ptrref) + assert isinstance(ind_ptr.source.expr.ptrref, irast.PointerRef) + rptr_specialization.add(ind_ptr.source.expr.ptrref) ptrs = [ptrcls_from_ptrref(ptrref, ctx=ctx) for ptrref in rptr_specialization] diff --git a/edb/edgeql/compiler/viewgen.py b/edb/edgeql/compiler/viewgen.py index 7cba99bee42..64bedf39f78 100644 --- a/edb/edgeql/compiler/viewgen.py +++ b/edb/edgeql/compiler/viewgen.py @@ -251,9 +251,9 @@ def _process_view( # Maybe rematerialize the set. The old ir_set might have already # been materialized, but the new version would be missing from the # use_sets. - if ir_set.rptr: + if isinstance(ir_set.expr, irast.Pointer): ctx.env.schema, remat_ptrcls = typeutils.ptrcls_from_ptrref( - ir_set.rptr.ptrref, schema=ctx.env.schema + ir_set.expr.ptrref, schema=ctx.env.schema ) setgen.maybe_materialize(remat_ptrcls, ir_set, ctx=ctx) @@ -2311,14 +2311,16 @@ def _get_late_shape_configuration( is_objtype = ir_set.path_id.is_objtype_path() if rptr is None: - rptr = ir_set.rptr - # If we have a specified rptr but no rptr on the set itself, - # construct a version of the set with the rptr added to use - # as the path tip for applying pointers. This ensures that - # we can find link properties on late shapes. - elif ir_set.rptr is None and ir_set.expr: + if isinstance(ir_set.expr, irast.Pointer): + rptr = ir_set.expr + elif ir_set.expr and not isinstance(ir_set.expr, irast.Pointer): + # If we have a specified rptr but set is not a pointer itself, + # construct a version of the set that is pointer so it can be used + # as the path tip for applying pointers. This ensures that + # we can find link properties on late shapes. ir_set = setgen.new_set_from_set( - ir_set, expr=rptr.replace(expr=ir_set.expr, is_phony=True), ctx=ctx) + ir_set, expr=rptr.replace(expr=ir_set.expr, is_phony=True), ctx=ctx + ) rptrcls: Optional[s_pointers.PointerLike] if rptr is not None: @@ -2370,8 +2372,8 @@ def late_compile_view_shapes( @late_compile_view_shapes.register(irast.Set) def _late_compile_view_shapes_in_set( ir_set: irast.Set, *, - rptr: Optional[irast.Pointer]=None, - parent_view_type: Optional[s_types.ExprType]=None, + rptr: Optional[irast.Pointer] = None, + parent_view_type: Optional[s_types.ExprType] = None, ctx: context.ContextLevel) -> None: shape_ptrs = _get_late_shape_configuration( @@ -2385,10 +2387,13 @@ def _late_compile_view_shapes_in_set( # # This is to avoid losing subquery distinctions (in cases # like test_edgeql_scope_tuple_15), and generally seems more natural. + is_definition_or_not_pointer = ( + not isinstance(ir_set.expr, irast.Pointer) or ir_set.expr.is_definition + ) expr = ir_set.old_expr if ( isinstance(expr, (irast.SelectStmt, irast.GroupStmt)) - and not (ir_set.rptr and not ir_set.rptr.is_definition) + and is_definition_or_not_pointer and (setgen.get_set_type(ir_set, ctx=ctx) == setgen.get_set_type(expr.result, ctx=ctx)) ): @@ -2400,9 +2405,12 @@ def _late_compile_view_shapes_in_set( with ctx.new() as scopectx: if set_scope is not None: scopectx.path_scope = set_scope + + if not rptr and isinstance(ir_set.expr, irast.Pointer): + rptr = ir_set.expr late_compile_view_shapes( child, - rptr=rptr or ir_set.rptr, + rptr=rptr, parent_view_type=parent_view_type, ctx=scopectx) @@ -2476,8 +2484,8 @@ def _late_compile_view_shapes_in_set( else: late_compile_view_shapes(expr, ctx=ctx) - elif isinstance(ir_set.rptr, irast.TupleIndirectionPointer): - late_compile_view_shapes(ir_set.rptr.source, ctx=ctx) + elif isinstance(ir_set.expr, irast.TupleIndirectionPointer): + late_compile_view_shapes(ir_set.expr.source, ctx=ctx) @late_compile_view_shapes.register(irast.SelectStmt) diff --git a/edb/ir/utils.py b/edb/ir/utils.py index 74f3b5c732d..2c6f4483976 100644 --- a/edb/ir/utils.py +++ b/edb/ir/utils.py @@ -40,6 +40,7 @@ from typing_extensions import TypeGuard import json +import uuid from edb import errors @@ -65,8 +66,8 @@ def get_longest_paths(ir: irast.Base) -> Set[irast.Set]: ir_sets = ast.find_children(ir, irast.Set, lambda n: n.old_expr is None) for ir_set in ir_sets: result.add(ir_set) - if ir_set.rptr: - parents.add(ir_set.rptr.source) + if isinstance(ir_set.expr, irast.Pointer): + parents.add(ir_set.expr.source) return result - parents @@ -173,10 +174,10 @@ def is_trivial_select(ir_expr: irast.Base) -> TypeGuard[irast.SelectStmt]: def unwrap_set(ir_set: irast.Set) -> irast.Set: - """If the give *ir_set* is an implicit SELECT wrapper, return the + """If the given *ir_set* is an implicit SELECT wrapper, return the wrapped set. """ - if ir_set.expr is not None and is_implicit_wrapper(ir_set.expr): + if is_implicit_wrapper(ir_set.expr): return ir_set.expr.result else: return ir_set @@ -184,8 +185,8 @@ def unwrap_set(ir_set: irast.Set) -> irast.Set: def get_path_root(ir_set: irast.Set) -> irast.Set: result = ir_set - while result.rptr is not None: - result = result.rptr.source + while isinstance(result.expr, irast.Pointer): + result = result.expr.source return result @@ -216,10 +217,9 @@ def is_type_intersection_reference(ir_expr: irast.Base) -> bool: """ if not isinstance(ir_expr, irast.Set): return False - - rptr = ir_expr.rptr - if rptr is None: + if not isinstance(ir_expr.expr, irast.Pointer): return False + rptr = ir_expr.expr ir_source = rptr.source @@ -247,7 +247,7 @@ def collapse_type_intersection( source = ir_set while True: - rptr = source.rptr + rptr = source.expr if not isinstance(rptr, irast.TypeIntersectionPointer): break result.append(rptr) @@ -435,11 +435,12 @@ def process_set(self, node: irast.Set) -> Set[irast.Set]: ): return set() - # XXX(rptr): Do this better results = [{node}] - results.append(self.visit(node.rptr)) - results.append(self.visit(node.shape)) - if node.rptr is None: + if isinstance(node.expr, irast.Pointer): + results.append(self.visit(node.expr)) + results.append(self.visit(node.shape)) + else: + results.append(self.visit(node.shape)) results.append(self.visit(node.expr)) # Bound variables are always potentially visible as are object @@ -508,3 +509,22 @@ def as_const(ir: irast.Base) -> Optional[irast.BaseConstant]: def is_set_instance(ir: irast.Set, typ: Type[T]) -> TypeGuard[irast.SetE[T]]: return isinstance(ir.expr, typ) + + +def ref_contains_multi(ref: irast.Set, singleton_id: uuid.UUID) -> bool: + while isinstance(ref.expr, irast.Pointer): + pointer: irast.Pointer = ref.expr + if pointer.dir_cardinality.is_multi(): + return True + + # We don't need to look further than the object that we know is a + # singleton. + if ( + singleton_id + and isinstance(pointer.ptrref, irast.PointerRef) + and pointer.ptrref.id == singleton_id + ): + break + ref = pointer.source + return False + diff --git a/edb/pgsql/compiler/astutils.py b/edb/pgsql/compiler/astutils.py index 4bede8f2cc5..62e11e541e1 100644 --- a/edb/pgsql/compiler/astutils.py +++ b/edb/pgsql/compiler/astutils.py @@ -41,12 +41,14 @@ def tuple_element_for_shape_el( *, ctx: context.CompilerContextLevel ) -> pgast.TupleElementBase: + from edb.ir import ast as irast + if shape_el.path_id.is_type_intersection_path(): - assert shape_el.rptr is not None - rptr = shape_el.rptr.source.rptr + assert isinstance(shape_el.expr, irast.Pointer) + rptr = shape_el.expr.source.expr else: - rptr = shape_el.rptr - assert rptr is not None + rptr = shape_el.expr + assert isinstance(rptr, irast.Pointer) ptrref = rptr.ptrref ptrname = ptrref.shortname diff --git a/edb/pgsql/compiler/expr.py b/edb/pgsql/compiler/expr.py index 2adb4f01298..34c03409461 100644 --- a/edb/pgsql/compiler/expr.py +++ b/edb/pgsql/compiler/expr.py @@ -787,9 +787,9 @@ def _compile_set_in_singleton_mode( elif node.old_expr is not None: return dispatch.compile(node.old_expr, ctx=ctx) else: - assert node.rptr - ptrref = node.rptr.ptrref - source = node.rptr.source + assert isinstance(node.expr, irast.Pointer) + ptrref = node.expr.ptrref + source = node.expr.source if isinstance(ptrref, irast.TupleIndirectionPointerRef): tuple_val = dispatch.compile(source, ctx=ctx) @@ -800,7 +800,7 @@ def _compile_set_in_singleton_mode( ) return set_expr - if ptrref.source_ptr is None and source.rptr is not None: + if ptrref.source_ptr is None and isinstance(source.expr, irast.Pointer): raise errors.UnsupportedFeatureError( 'unexpectedly long path in simple expr') @@ -817,7 +817,7 @@ def _compile_set_in_singleton_mode( colref = pgast.ColumnRef( name=rvar_name + [ptr_stor_info.column_name], - nullable=node.rptr.dir_cardinality.can_be_zero()) + nullable=node.expr.dir_cardinality.can_be_zero()) return colref diff --git a/edb/pgsql/compiler/shapecomp.py b/edb/pgsql/compiler/shapecomp.py index fff49d0bc05..a71974f8baf 100644 --- a/edb/pgsql/compiler/shapecomp.py +++ b/edb/pgsql/compiler/shapecomp.py @@ -80,8 +80,8 @@ def compile_shape( if op == qlast.ShapeOp.MATERIALIZE and not ctx.materializing: continue - rptr = el.rptr - assert rptr is not None + rptr = el.expr + assert isinstance(rptr, irast.Pointer) ptrref = rptr.ptrref # As an implementation expedient, we currently represent # AT_MOST_ONE materialized values with arrays diff --git a/edb/schema/indexes.py b/edb/schema/indexes.py index 2137204ed24..f08deb56342 100644 --- a/edb/schema/indexes.py +++ b/edb/schema/indexes.py @@ -632,7 +632,6 @@ def compile_expr_field( track_schema_ref_exprs: bool=False, ) -> s_expr.CompiledExpression: from edb.ir import utils as irutils - from edb.ir import ast as irast if field.name in {'expr', 'except_expr'}: # type ignore below, for the class is used as mixin @@ -678,21 +677,11 @@ def compile_expr_field( has_multi = False for ref in refs: assert subject - while ref.rptr: - rptr = ref.rptr - if rptr.dir_cardinality.is_multi(): - has_multi = True - - # We don't need to look further than the subject, - # which is always valid. (And which is a singleton - # in an index expression if it is itself a - # singleton, regardless of other parts of the path.) - if ( - isinstance(rptr.ptrref, irast.PointerRef) - and rptr.ptrref.id == subject.id - ): - break - ref = rptr.source + # Subject is a singleton in an index expression if it is itself + # a singleton, regardless of other parts of the path. + if irutils.ref_contains_multi(ref, subject.id): + has_multi = True + break if has_multi and irutils.contains_set_of_op(expr.irast): raise errors.SchemaDefinitionError( diff --git a/edb/server/compiler/ddl.py b/edb/server/compiler/ddl.py index 6ca78a87b61..857b242a8c0 100644 --- a/edb/server/compiler/ddl.py +++ b/edb/server/compiler/ddl.py @@ -1315,13 +1315,13 @@ def administer_reindex( if ( not expr.expr or not isinstance(expr.expr, irast.SelectStmt) - or not expr.expr.result.rptr + or not isinstance(expr.expr.result.expr, irast.Pointer) ): raise errors.QueryError( 'invalid pointer argument to reindex()', span=arg.span, ) - rptr = expr.expr.result.rptr + rptr = expr.expr.result.expr source = rptr.source else: rptr = None @@ -1465,13 +1465,13 @@ def administer_vacuum( if ( not expr.expr or not isinstance(expr.expr, irast.SelectStmt) - or not expr.expr.result.rptr + or not isinstance(expr.expr.result.expr, irast.Pointer) ): raise errors.QueryError( 'invalid pointer argument to vacuum()', span=arg.span, ) - rptr = expr.expr.result.rptr + rptr = expr.expr.result.expr source = rptr.source else: rptr = None From f1f4a391280c9a67b693f30454a23df7f64931d7 Mon Sep 17 00:00:00 2001 From: "Michael J. Sullivan" Date: Mon, 25 Mar 2024 13:13:31 -0700 Subject: [PATCH 3/8] Fix "Delta Plan Input" debug spew, perform one fewer apply in some cases (#7107) delta_from_ddl computes the new schema and canonicalizes the delta tree already. To print the pre-canonicalization delta, we need to do it inside delta_from_ddl. Also, we can return and use that schema directly. --- edb/schema/ddl.py | 11 ++++++++--- edb/server/compiler/ddl.py | 29 ++++++----------------------- 2 files changed, 14 insertions(+), 26 deletions(-) diff --git a/edb/schema/ddl.py b/edb/schema/ddl.py index b6590c2a345..8cdc9fd08ec 100644 --- a/edb/schema/ddl.py +++ b/edb/schema/ddl.py @@ -36,6 +36,7 @@ from edb import errors from edb import edgeql +from edb.common import debug from edb.common import uuidgen from edb.common import verutils from edb.edgeql import ast as qlast @@ -623,7 +624,7 @@ def apply_ddl_script_ex( for ddl_stmt in edgeql.parse_block(ddl_text): if not isinstance(ddl_stmt, qlast.DDLCommand): raise AssertionError(f'expected DDLCommand node, got {ddl_stmt!r}') - schema, cmd = _delta_from_ddl( + schema, cmd = delta_and_schema_from_ddl( ddl_stmt, schema=schema, modaliases=modaliases, @@ -653,7 +654,7 @@ def delta_from_ddl( ]=None, compat_ver: Optional[verutils.Version] = None, ) -> sd.DeltaRoot: - _, cmd = _delta_from_ddl( + _, cmd = delta_and_schema_from_ddl( ddl_stmt, schema=schema, modaliases=modaliases, @@ -666,7 +667,7 @@ def delta_from_ddl( return cmd -def _delta_from_ddl( +def delta_and_schema_from_ddl( ddl_stmt: qlast.DDLCommand, *, schema: s_schema.Schema, @@ -700,6 +701,10 @@ def _delta_from_ddl( context=context, testmode=testmode, ) + if debug.flags.delta_plan: + debug.header('Delta Plan Input') + debug.dump(cmd) + schema = cmd.apply(schema, context) if not stdmode: diff --git a/edb/server/compiler/ddl.py b/edb/server/compiler/ddl.py index 857b242a8c0..5c56a864009 100644 --- a/edb/server/compiler/ddl.py +++ b/edb/server/compiler/ddl.py @@ -113,7 +113,7 @@ def compile_and_apply_ddl_stmt( return compile_and_apply_ddl_stmt(ctx, cm) assert isinstance(stmt, qlast.DDLCommand) - delta = s_ddl.delta_from_ddl( + new_schema, delta = s_ddl.delta_and_schema_from_ddl( stmt, schema=schema, modaliases=current_tx.get_modaliases(), @@ -121,17 +121,14 @@ def compile_and_apply_ddl_stmt( ) if debug.flags.delta_plan: - debug.header('Delta Plan Input') - debug.dump(delta) + debug.header('Canonical Delta Plan') + debug.dump(delta, schema=schema) if mstate := current_tx.get_migration_state(): mstate = mstate._replace( accepted_cmds=mstate.accepted_cmds + (stmt,), ) - context = _new_delta_context(ctx) - schema = delta.apply(schema, context=context) - last_proposed = mstate.last_proposed if last_proposed: if last_proposed[0].required_user_input or last_proposed[ @@ -159,7 +156,7 @@ def compile_and_apply_ddl_stmt( mstate = mstate._replace(last_proposed=None) current_tx.update_migration_state(mstate) - current_tx.update_schema(schema) + current_tx.update_schema(new_schema) return dbstate.DDLQuery( sql=(b'SELECT LIMIT 0',), @@ -182,10 +179,7 @@ def compile_and_apply_ddl_stmt( ) current_tx.update_migration_rewrite_state(mrstate) - context = _new_delta_context(ctx) - schema = delta.apply(schema, context=context) - - current_tx.update_schema(schema) + current_tx.update_schema(new_schema) return dbstate.DDLQuery( sql=(b'SELECT LIMIT 0',), @@ -193,13 +187,6 @@ def compile_and_apply_ddl_stmt( is_transactional=True, ) - # Do a dry-run on test_schema to canonicalize - # the schema delta-commands. - test_schema = current_tx.get_schema(ctx.compiler_state.std_schema) - context = _new_delta_context(ctx) - delta.apply(test_schema, context=context) - delta.canonical = True - # Apply and adapt delta, build native delta plan, which # will also update the schema. block, new_types, config_ops = _process_delta(ctx, delta) @@ -301,7 +288,7 @@ def _new_delta_context( def _get_delta_context_args(ctx: compiler.CompileContext) -> dict[str, Any]: - """Get the args need from delta_from_ddl""" + """Get the args needed for delta_and_schema_from_ddl""" return dict( testmode=compiler._get_config_val(ctx, '__internal_testmode'), allow_dml_in_functions=( @@ -320,10 +307,6 @@ def _process_delta( current_tx = ctx.state.current_tx() schema = current_tx.get_schema(ctx.compiler_state.std_schema) - if debug.flags.delta_plan: - debug.header('Canonical Delta Plan') - debug.dump(delta, schema=schema) - pgdelta = pg_delta.CommandMeta.adapt(delta) assert isinstance(pgdelta, pg_delta.DeltaRoot) context = _new_delta_context(ctx) From a02fc5aee6a1e3eb07e05dfcc6b8d7aa068c1f1c Mon Sep 17 00:00:00 2001 From: "Michael J. Sullivan" Date: Mon, 25 Mar 2024 17:09:40 -0700 Subject: [PATCH 4/8] Better typing for _special_case function implementations in relgen (#7115) We can give the incoming set the type `SetE[Call]`, which lets us skip a mypy-appeasing assert in nearly every function. --- edb/pgsql/compiler/relgen.py | 54 ++++++++++++------------------------ 1 file changed, 18 insertions(+), 36 deletions(-) diff --git a/edb/pgsql/compiler/relgen.py b/edb/pgsql/compiler/relgen.py index 872b2c456d4..254d726a7a0 100644 --- a/edb/pgsql/compiler/relgen.py +++ b/edb/pgsql/compiler/relgen.py @@ -322,7 +322,9 @@ def _process_toplevel_query( class _SpecialCaseFunc(Protocol): def __call__( - self, ir_set: irast.Set, *, ctx: context.CompilerContextLevel + self, ir_set: irast.SetE[irast.Call], + *, + ctx: context.CompilerContextLevel, ) -> SetRVars: pass @@ -1494,7 +1496,7 @@ def process_set_as_subquery( @_special_case('std::IN') @_special_case('std::NOT IN') def process_set_as_membership_expr( - ir_set: irast.Set, *, ctx: context.CompilerContextLevel + ir_set: irast.SetE[irast.Call], *, ctx: context.CompilerContextLevel ) -> SetRVars: expr = ir_set.expr assert isinstance(expr, irast.OperatorCall) @@ -1594,10 +1596,9 @@ def process_set_as_membership_expr( @_special_case('std::EXCEPT') @_special_case('std::INTERSECT') def process_set_as_setop( - ir_set: irast.Set, *, ctx: context.CompilerContextLevel + ir_set: irast.SetE[irast.Call], *, ctx: context.CompilerContextLevel ) -> SetRVars: expr = ir_set.expr - assert isinstance(expr, irast.OperatorCall) with ctx.new() as newctx: newctx.expr_exposed = False @@ -1644,11 +1645,10 @@ def process_set_as_setop( @_special_case('std::DISTINCT') def process_set_as_distinct( - ir_set: irast.Set, *, ctx: context.CompilerContextLevel + ir_set: irast.SetE[irast.Call], *, ctx: context.CompilerContextLevel ) -> SetRVars: expr = ir_set.expr stmt = ctx.rel - assert isinstance(expr, irast.OperatorCall) with ctx.subrel() as subctx: subqry = subctx.rel @@ -1676,12 +1676,11 @@ def process_set_as_distinct( @_special_case('std::IF') def process_set_as_ifelse( - ir_set: irast.Set, *, ctx: context.CompilerContextLevel + ir_set: irast.SetE[irast.Call], *, ctx: context.CompilerContextLevel ) -> SetRVars: # A IF Cond ELSE B is transformed into: # SELECT A WHERE Cond UNION ALL SELECT B WHERE NOT Cond expr = ir_set.expr - assert isinstance(expr, irast.OperatorCall) stmt = ctx.rel if_expr, condition, else_expr = (a.expr for a in expr.args) @@ -1787,10 +1786,9 @@ def process_set_as_ifelse( @_special_case('std::??') def process_set_as_coalesce( - ir_set: irast.Set, *, ctx: context.CompilerContextLevel + ir_set: irast.SetE[irast.Call], *, ctx: context.CompilerContextLevel ) -> SetRVars: expr = ir_set.expr - assert isinstance(expr, irast.OperatorCall) with ctx.new() as newctx: newctx.expr_exposed = False @@ -2195,12 +2193,11 @@ def process_set_as_expr( @_special_case('std::assert_single') def process_set_as_singleton_assertion( - ir_set: irast.Set, + ir_set: irast.SetE[irast.Call], *, ctx: context.CompilerContextLevel, ) -> SetRVars: expr = ir_set.expr - assert isinstance(expr, irast.FunctionCall) stmt = ctx.rel msg_arg = expr.args[0] @@ -2304,13 +2301,12 @@ def process_set_as_singleton_assertion( @_special_case('std::assert_exists') def process_set_as_existence_assertion( - ir_set: irast.Set, + ir_set: irast.SetE[irast.Call], *, ctx: context.CompilerContextLevel, ) -> SetRVars: """Implementation of std::assert_exists""" expr = ir_set.expr - assert isinstance(expr, irast.FunctionCall) stmt = ctx.rel msg_arg = expr.args[0] @@ -2393,14 +2389,12 @@ def process_set_as_existence_assertion( @_special_case('std::assert_distinct') def process_set_as_multiplicity_assertion( - ir_set: irast.Set, + ir_set: irast.SetE[irast.Call], *, ctx: context.CompilerContextLevel, ) -> SetRVars: """Implementation of std::assert_distinct""" expr = ir_set.expr - assert isinstance(expr, irast.FunctionCall) - msg_arg = expr.args[0] ir_arg = expr.args[1] ir_arg_set = ir_arg.expr @@ -2632,11 +2626,9 @@ def process_set_as_simple_enumerate( @_special_case('std::enumerate') def process_set_as_enumerate( - ir_set: irast.Set, *, ctx: context.CompilerContextLevel + ir_set: irast.SetE[irast.Call], *, ctx: context.CompilerContextLevel ) -> SetRVars: - assert isinstance(ir_set.expr, irast.FunctionCall) expr = ir_set.expr - arg_set = expr.args[0].expr arg_expr = arg_set.expr arg_subj = irutils.unwrap_set(arg_set).expr @@ -2664,7 +2656,7 @@ def process_set_as_enumerate( @_special_case('std::max', only_as_fallback=True) @_special_case('std::min', only_as_fallback=True) def process_set_as_std_min_max( - ir_set: irast.Set, + ir_set: irast.SetE[irast.Call], *, ctx: context.CompilerContextLevel, ) -> SetRVars: @@ -2688,8 +2680,6 @@ def process_set_as_std_min_max( # type. expr = ir_set.expr - assert isinstance(expr, irast.FunctionCall) - with ctx.subrel() as newctx: ir_arg = expr.args[0].expr dispatch.visit(ir_arg, ctx=newctx) @@ -2841,7 +2831,7 @@ def process_set_as_std_range( @_special_case('std::multirange', only_as_fallback=True) def process_set_as_std_multirange( - ir_set: irast.Set, + ir_set: irast.SetE[irast.Call], *, ctx: context.CompilerContextLevel, ) -> SetRVars: @@ -2855,7 +2845,6 @@ def process_set_as_std_multirange( # # (variadic ranges) expr = ir_set.expr - assert isinstance(expr, irast.FunctionCall) ranges = dispatch.compile(expr.args[0].expr, ctx=ctx) pg_type = pg_types.pg_type_from_ir_typeref(expr.typeref) @@ -3637,11 +3626,9 @@ def process_set_as_agg_expr( @_special_case('std::EXISTS') def process_set_as_exists_expr( - ir_set: irast.Set, *, ctx: context.CompilerContextLevel + ir_set: irast.SetE[irast.Call], *, ctx: context.CompilerContextLevel ) -> SetRVars: expr = ir_set.expr - assert isinstance(expr, irast.OperatorCall) - with ctx.subrel() as subctx: wrapper = subctx.rel subctx.expr_exposed = False @@ -3662,12 +3649,9 @@ def process_set_as_exists_expr( @_special_case('std::json_object_pack') def process_set_as_json_object_pack( - ir_set: irast.Set, *, ctx: context.CompilerContextLevel + ir_set: irast.SetE[irast.Call], *, ctx: context.CompilerContextLevel ) -> SetRVars: - expr = ir_set.expr - assert isinstance(expr, irast.FunctionCall) - - ir_arg = expr.args[0].expr + ir_arg = ir_set.expr.args[0].expr # compile IR to pg AST dispatch.visit(ir_arg, ctx=ctx) @@ -3838,11 +3822,9 @@ def process_encoded_param( @_special_case('fts::search') def process_set_as_fts_search( - ir_set: irast.Set, *, ctx: context.CompilerContextLevel + ir_set: irast.SetE[irast.Call], *, ctx: context.CompilerContextLevel ) -> SetRVars: func_call = ir_set.expr - assert isinstance(func_call, irast.FunctionCall) - with ctx.subrel() as newctx: newctx.expr_exposed = False From cfc766006cc1999f5ad824ff6ffe72044ff103af Mon Sep 17 00:00:00 2001 From: "Michael J. Sullivan" Date: Mon, 25 Mar 2024 21:20:33 -0700 Subject: [PATCH 5/8] Don't pretty print SQL text by default (#7117) Only do it during the debug endpoints. I think this changed by accident at some point. This shouldn't matter *too* much, but will reduce the size of the SQL sent to postgres. --- edb/pgsql/codegen.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/edb/pgsql/codegen.py b/edb/pgsql/codegen.py index dad82511d4d..f9be615816c 100644 --- a/edb/pgsql/codegen.py +++ b/edb/pgsql/codegen.py @@ -86,7 +86,7 @@ def generate_source( *, indent_with: str = ' ' * 4, add_line_information: bool = False, - pretty: bool = True, + pretty: bool = False, reordered: bool = False, ) -> str: # Simplified entrypoint From f6306a14f762a4ed075a6610af7002be6cae0e8c Mon Sep 17 00:00:00 2001 From: "Michael J. Sullivan" Date: Tue, 26 Mar 2024 08:17:10 -0700 Subject: [PATCH 6/8] Refactor Set.rptr into Set.expr (2 / 2) (#7114) Get rid of rptr and old_expr. (old_expr becomes irutils.sub_expr and gets used in some call sites) --- edb/edgeql/compiler/conflicts.py | 2 - edb/edgeql/compiler/group.py | 12 +-- edb/edgeql/compiler/inference/cardinality.py | 8 +- edb/edgeql/compiler/inference/multiplicity.py | 6 +- edb/edgeql/compiler/setgen.py | 3 - edb/edgeql/compiler/stmtctx.py | 4 +- edb/edgeql/compiler/viewgen.py | 24 +++--- edb/ir/ast.py | 29 +------ edb/ir/utils.py | 18 +++- edb/pgsql/compiler/config.py | 13 ++- edb/pgsql/compiler/dml.py | 74 +++++++--------- edb/pgsql/compiler/expr.py | 86 +++++++++++-------- edb/pgsql/compiler/relctx.py | 47 +++++----- edb/pgsql/compiler/relgen.py | 57 ++++++------ edb/pgsql/compiler/shapecomp.py | 3 +- edb/pgsql/schemamech.py | 23 ++--- edb/schema/constraints.py | 12 +-- edb/schema/pointers.py | 15 ++-- tests/test_edgeql_ir_card_inference.py | 2 +- 19 files changed, 217 insertions(+), 221 deletions(-) diff --git a/edb/edgeql/compiler/conflicts.py b/edb/edgeql/compiler/conflicts.py index 63dd77c7c45..33fec9e9ce5 100644 --- a/edb/edgeql/compiler/conflicts.py +++ b/edb/edgeql/compiler/conflicts.py @@ -593,7 +593,6 @@ def compile_insert_unless_conflict_on( def _has_explicit_id_write(stmt: irast.MutatingStmt) -> bool: for elem, _ in stmt.subject.shape: - assert isinstance(elem.expr, irast.Pointer) if elem.expr.ptrref.shortname.name == 'id': return elem.span is not None return False @@ -656,7 +655,6 @@ def _compile_inheritance_conflict_selects( shape_ptrs = set() for elem, op in stmt.subject.shape: - assert isinstance(elem.expr, irast.Pointer) if op != qlast.ShapeOp.MATERIALIZE: shape_ptrs.add(elem.expr.ptrref.shortname.name) diff --git a/edb/edgeql/compiler/group.py b/edb/edgeql/compiler/group.py index 3bcd6163634..4f7d691426a 100644 --- a/edb/edgeql/compiler/group.py +++ b/edb/edgeql/compiler/group.py @@ -129,18 +129,20 @@ def visit_Set(self, node: irast.Set, skip_rptr: bool=False) -> None: self.visit(node.shape) - # XXX: old_expr if isinstance(node.expr, irast.Pointer): - if not node.old_expr: + sub_expr = node.expr.expr + if not sub_expr: self.visit(node.expr.source) else: if node.expr.source.path_id not in self.seen: self.seen[node.expr.source.path_id] = False + else: + sub_expr = node.expr - if isinstance(node.expr, irast.Call): - self.process_call(node.expr, node) + if isinstance(sub_expr, irast.Call): + self.process_call(sub_expr, node) else: - self.visit(node.old_expr) + self.visit(sub_expr) self.aggregate = old self.scope_tree = old_scope diff --git a/edb/edgeql/compiler/inference/cardinality.py b/edb/edgeql/compiler/inference/cardinality.py index c91fd14baf1..31b839cf5f0 100644 --- a/edb/edgeql/compiler/inference/cardinality.py +++ b/edb/edgeql/compiler/inference/cardinality.py @@ -587,9 +587,9 @@ def _infer_set_inner( new_scope = inf_utils.get_set_scope(ir, scope_tree, ctx=ctx) # TODO: Migrate to Pointer-as-Expr well, and not half-assedly. - if ir.old_expr: - expr_card = infer_cardinality( - ir.old_expr, scope_tree=new_scope, ctx=ctx) + sub_expr = irutils.sub_expr(ir) + if sub_expr: + expr_card = infer_cardinality(sub_expr, scope_tree=new_scope, ctx=ctx) if isinstance(ir.expr, irast.Pointer) and not ir.expr.is_phony: ptr = ir.expr @@ -634,7 +634,7 @@ def _infer_set_inner( elif isinstance(ir, irast.EmptySet): card = AT_MOST_ONE - elif ir.old_expr is not None: + elif sub_expr is not None: card = expr_card else: # The only things that should be here without an expression or diff --git a/edb/edgeql/compiler/inference/multiplicity.py b/edb/edgeql/compiler/inference/multiplicity.py index a39003502a4..10fc2714906 100644 --- a/edb/edgeql/compiler/inference/multiplicity.py +++ b/edb/edgeql/compiler/inference/multiplicity.py @@ -256,11 +256,11 @@ def _infer_set_inner( new_scope = inf_utils.get_set_scope(ir, scope_tree, ctx=ctx) # TODO: Migrate to Pointer-as-Expr well, and not half-assedly. - if ir.old_expr is None: + sub_expr = irutils.sub_expr(ir) + if sub_expr is None: expr_mult = None else: - expr_mult = infer_multiplicity( - ir.old_expr, scope_tree=new_scope, ctx=ctx) + expr_mult = infer_multiplicity(sub_expr, scope_tree=new_scope, ctx=ctx) if isinstance(ir.expr, irast.Pointer): ptr = ir.expr diff --git a/edb/edgeql/compiler/setgen.py b/edb/edgeql/compiler/setgen.py index 1edd7d61db9..1cf3adc8e28 100644 --- a/edb/edgeql/compiler/setgen.py +++ b/edb/edgeql/compiler/setgen.py @@ -1265,9 +1265,6 @@ def type_intersection_set( elif stype.issubclass(ctx.env.schema, component_endpoint): assert isinstance(stype, s_objtypes.ObjectType) if rptr.direction is s_pointers.PointerDirection.Inbound: - # assert isinstance(component, irast.PointerRef) - # rptr_specialization.append(component) - narrow_ptr = stype.getptr( ctx.env.schema, component.shortname.get_local_name(), diff --git a/edb/edgeql/compiler/stmtctx.py b/edb/edgeql/compiler/stmtctx.py index e3c41bdb372..0b2693230df 100644 --- a/edb/edgeql/compiler/stmtctx.py +++ b/edb/edgeql/compiler/stmtctx.py @@ -403,8 +403,8 @@ def _fixup_materialized_sets( assert ( not any(use.src_path() for use in mat_set.uses) - or mat_set.materialized.rptr - ), f"materialized ptr {mat_set.uses} missing rptr" + or isinstance(mat_set.materialized.expr, irast.Pointer) + ), f"materialized ptr {mat_set.uses} missing pointer" mat_set.finalized = True return to_clear diff --git a/edb/edgeql/compiler/viewgen.py b/edb/edgeql/compiler/viewgen.py index 64bedf39f78..0031d4d3e53 100644 --- a/edb/edgeql/compiler/viewgen.py +++ b/edb/edgeql/compiler/viewgen.py @@ -535,7 +535,6 @@ def _process_view( ns=ctx.path_id_namespace, ctx=ctx, ) - # XXX: I THINK THIS IS FINE?? assert not isinstance(ptr_set.expr, irast.Pointer) ptr_set.expr = irast.Pointer( source=ir_set, @@ -978,12 +977,10 @@ def get_anchors(stype: s_objtypes.ObjectType) -> RewriteAnchors: ptr_set = setgen.new_set_from_set( target, path_id=path_id, - # rptr=None, ctx=ctx, ) # construct a new set with correct path_id - # XXX: CHECK?x ptr_set.expr = irast.Pointer( source=ir_set, expr=ptr_set.expr, @@ -991,6 +988,7 @@ def get_anchors(stype: s_objtypes.ObjectType) -> RewriteAnchors: ptrref=actual_ptrref, is_definition=True, ) + assert irutils.is_set_instance(ptr_set, irast.Pointer) by_type[ty][pn] = (ptr_set, ptrref.real_material_ptr) @@ -1679,14 +1677,15 @@ def _normalize_view_ptr_expr( ) ) - # XXX: THIS IS MAD DODGY - old_rptr = irexpr.rptr - if old_rptr: - irexpr.expr = old_rptr.expr + # HACK: This is mad dodgy. Hide the Pointer + # when compiling. + old_expr = irexpr.expr + if isinstance(old_expr, irast.Pointer): + irexpr.expr = old_expr.expr irexpr = dispatch.compile(cast_qlexpr, ctx=subctx) - if old_rptr: - old_rptr.expr = irexpr.expr - irexpr.expr = old_rptr + if isinstance(old_expr, irast.Pointer): + old_expr.expr = irexpr.expr + irexpr.expr = old_expr else: expected = [ @@ -2220,7 +2219,7 @@ def _get_shape_configuration_inner( or (ctx.implicit_id_in_shapes and not is_mutation) # we are inside an UPDATE shape and this is # an explicit expression (link target update) - or (is_parent_update and ir_set.old_expr is not None) + or (is_parent_update and irutils.sub_expr(ir_set) is not None) or all_materialize ) # We actually *always* inject an implicit id, but it's just @@ -2390,7 +2389,7 @@ def _late_compile_view_shapes_in_set( is_definition_or_not_pointer = ( not isinstance(ir_set.expr, irast.Pointer) or ir_set.expr.is_definition ) - expr = ir_set.old_expr + expr = irutils.sub_expr(ir_set) if ( isinstance(expr, (irast.SelectStmt, irast.GroupStmt)) and is_definition_or_not_pointer @@ -2474,7 +2473,6 @@ def _late_compile_view_shapes_in_set( ir_set.shape = tuple(shape) - # XXX: old_expr elif expr is not None: set_scope = pathctx.get_set_scope(ir_set, ctx=ctx) if set_scope is not None: diff --git a/edb/ir/ast.py b/edb/ir/ast.py index 82d5cf874ae..45d37cf6f02 100644 --- a/edb/ir/ast.py +++ b/edb/ir/ast.py @@ -534,18 +534,16 @@ class TypeRoot(Expr): # SetE is the base 'Set' type, and it is parameterized over what kind -# of expression it might hold. Most code uses the Set alias below, which +# of expression it holds. Most code uses the Set alias below, which # instantiates it with Optional[Expr]. # irutils.is_set_instance can be used to refine the type. class SetE(Base, typing.Generic[T_co]): '''A somewhat overloaded metadata container for expressions. - Its primary notional purpose is to be the holder for expression metadata + Its primary purpose is to be the holder for expression metadata such as path_id. - It *also*, when rptr is set, represents pointer dereferences. - - Also also, it contains shape applications. + It *also* contains shape applications. ''' __ast_frozen_fields__ = frozenset({'typeref'}) @@ -582,25 +580,6 @@ class SetE(Base, typing.Generic[T_co]): # to join against target types on links, and to ensure rvars. ignore_rewrites: bool = False - # TODO: We would like to get rid of this a medium amount. - # It is to ease our migration towards Pointer being an expression. - @property - def rptr(self) -> typing.Optional[Pointer]: - if isinstance(self.expr, Pointer): - return self.expr - else: - return None - - # XXX: We would like to get rid of this very much. - # It behaves like the expr field did before we moved Pointer into - # expr, and enables some really half-ass migrations. - @property - def old_expr(self) -> typing.Optional[Expr]: - if isinstance(self.expr, Pointer): - return self.expr.expr - else: - return self.expr - def __repr__(self) -> str: return f'' @@ -1274,7 +1253,7 @@ def typeref(self) -> TypeRef: # N.B: The PointerRef corresponds to the *definition* point of the rewrite. -RewritesOfType = typing.Dict[str, typing.Tuple[Set, BasePointerRef]] +RewritesOfType = typing.Dict[str, typing.Tuple[SetE[Pointer], BasePointerRef]] @dataclasses.dataclass(kw_only=True, frozen=True) diff --git a/edb/ir/utils.py b/edb/ir/utils.py index 2c6f4483976..fd3e49bed77 100644 --- a/edb/ir/utils.py +++ b/edb/ir/utils.py @@ -62,8 +62,11 @@ def get_longest_paths(ir: irast.Base) -> Set[irast.Set]: result = set() parents = set() - # XXX - ir_sets = ast.find_children(ir, irast.Set, lambda n: n.old_expr is None) + ir_sets = ast.find_children( + ir, + irast.Set, + lambda n: sub_expr(n) is None or isinstance(n.expr, irast.TypeRoot), + ) for ir_set in ir_sets: result.add(ir_set) if isinstance(ir_set.expr, irast.Pointer): @@ -528,3 +531,14 @@ def ref_contains_multi(ref: irast.Set, singleton_id: uuid.UUID) -> bool: ref = pointer.source return False + +def sub_expr(ir: irast.Set) -> Optional[irast.Expr]: + """Fetch the "sub-expression" of a set. + + For a non-pointer Set, it's just the expr, but for a Pointer + it is the optional computed expression. + """ + if isinstance(ir.expr, irast.Pointer): + return ir.expr.expr + else: + return ir.expr diff --git a/edb/pgsql/compiler/config.py b/edb/pgsql/compiler/config.py index 77cb79ca1ed..8eb655b1bbc 100644 --- a/edb/pgsql/compiler/config.py +++ b/edb/pgsql/compiler/config.py @@ -395,11 +395,10 @@ def compile_ConfigReset( # have the information at hand. # XXX: Do we need to consider _tname also? keys = [ - el.rptr.ptrref.shortname.name + el.expr.ptrref.shortname.name for el, op in sel_expr.result.shape - if el.rptr - and op == qlast.ShapeOp.ASSIGN - and not irtyputils.is_object(el.rptr.ptrref.out_target) + if op == qlast.ShapeOp.ASSIGN + and not irtyputils.is_object(el.expr.ptrref.out_target) ] newval = pgast.SelectStmt( @@ -593,8 +592,7 @@ def _rewrite_config_insert( # so the autogenerated id must not be returned. ir_set.shape = tuple(filter( lambda el: ( - (rptr := el[0].rptr) is not None - and rptr.ptrref.shortname.name != 'id' + el[0].expr.ptrref.shortname.name != 'id' ), ir_set.shape, )) @@ -603,8 +601,7 @@ def _rewrite_config_insert( if isinstance(el.expr.expr, irast.InsertStmt): el.shape = tuple(filter( lambda e: ( - (rptr := e[0].rptr) is not None - and rptr.ptrref.shortname.name != 'id' + e[0].expr.ptrref.shortname.name != 'id' ), el.shape, )) diff --git a/edb/pgsql/compiler/dml.py b/edb/pgsql/compiler/dml.py index 25b9414cd45..eda4b215c96 100644 --- a/edb/pgsql/compiler/dml.py +++ b/edb/pgsql/compiler/dml.py @@ -702,7 +702,7 @@ def process_insert_body( pathctx.put_path_value_rvar(select, ir_stmt.subject.path_id, fallback_rvar) # compile contents CTE - elements: List[Tuple[irast.Set, irast.BasePointerRef]] = [] + elements: List[Tuple[irast.SetE[irast.Pointer], irast.BasePointerRef]] = [] for shape_el, shape_op in ir_stmt.subject.shape: assert shape_op is qlast.ShapeOp.ASSIGN @@ -711,11 +711,10 @@ def process_insert_body( if shape_el.path_id.is_linkprop_path(): continue - assert shape_el.rptr is not None - ptrref = shape_el.rptr.ptrref + ptrref = shape_el.expr.ptrref if ptrref.material_ptr is not None: ptrref = ptrref.material_ptr - assert shape_el.old_expr # XXX + assert shape_el.expr.expr elements.append((shape_el, ptrref)) external_inserts = process_insert_shape( @@ -723,7 +722,7 @@ def process_insert_body( ) single_external = [ ir for ir in external_inserts - if ir.rptr and ir.rptr.dir_cardinality.is_single() + if ir.expr.dir_cardinality.is_single() ] # Put the select that builds the tuples to insert into its own CTE. @@ -902,8 +901,8 @@ def process_insert_rewrites( iterator: Optional[pgast.IteratorCTE], inner_iterator: Optional[pgast.IteratorCTE], rewrites: irast.RewritesOfType, - single_external: List[irast.Set], - elements: List[Tuple[irast.Set, irast.BasePointerRef]], + single_external: List[irast.SetE[irast.Pointer]], + elements: Sequence[Tuple[irast.SetE[irast.Pointer], irast.BasePointerRef]], ctx: context.CompilerContextLevel, ) -> tuple[pgast.CommonTableExpr, pgast.PathRangeVar]: typeref = ir_stmt.subject.typeref.real_material_type @@ -961,8 +960,7 @@ def process_insert_rewrites( # populate the field from the link overlays. handled = set(rewrites) for ext_ir in single_external: - assert ext_ir.rptr - handled.add(ext_ir.rptr.ptrref.shortname.name) + handled.add(ext_ir.expr.ptrref.shortname.name) with ctx.subrel() as ectx: ext_rvar = relctx.new_pointer_rvar( ext_ir, link_bias=True, src_rvar=iterator_rvar, ctx=ectx) @@ -972,10 +970,10 @@ def process_insert_rewrites( ectx.rel, ext_ir.path_id, env=ctx.env) ptr_info = pg_types.get_ptrref_storage_info( - ext_ir.rptr.ptrref, resolve_type=True, link_bias=False) + ext_ir.expr.ptrref, resolve_type=True, link_bias=False) rew_stmt.target_list.append(pgast.ResTarget( name=ptr_info.column_name, val=ectx.rel)) - nptr_map[ext_ir.rptr.ptrref.real_material_ptr.name] = ectx.rel + nptr_map[ext_ir.expr.ptrref.real_material_ptr.name] = ectx.rel # Pull in pointers that were not rewritten not_rewritten = { @@ -1010,12 +1008,12 @@ def process_insert_shape( ir_stmt: irast.InsertStmt, select: pgast.SelectStmt, ptr_map: Dict[sn.Name, pgast.BaseExpr], - elements: List[Tuple[irast.Set, irast.BasePointerRef]], + elements: Sequence[Tuple[irast.SetE[irast.Pointer], irast.BasePointerRef]], iterator: Optional[pgast.IteratorCTE], inner_iterator: Optional[pgast.IteratorCTE], ctx: context.CompilerContextLevel, force_optional: bool=False, -) -> List[irast.Set]: +) -> List[irast.SetE[irast.Pointer]]: # Compile the shape external_inserts = [] @@ -1088,7 +1086,7 @@ def process_insert_shape( def compile_insert_shape_element( - shape_el: irast.Set, + shape_el: irast.SetE[irast.Pointer], *, ir_stmt: irast.MutatingStmt, iterator_id: Optional[pgast.BaseExpr], @@ -1097,8 +1095,6 @@ def compile_insert_shape_element( ) -> None: with ctx.new() as insvalctx: - assert shape_el.rptr is not None - if iterator_id is not None: id = iterator_id insvalctx.volatility_ref = (lambda _stmt, _ctx: id,) @@ -1111,7 +1107,7 @@ def compile_insert_shape_element( insvalctx.current_insert_path_id = ir_stmt.subject.path_id - if shape_el.rptr.dir_cardinality.can_be_zero() or force_optional: + if shape_el.expr.dir_cardinality.can_be_zero() or force_optional: # If the element can be empty, compile it in a subquery to force it # to be NULL. value = relgen.set_as_subquery( @@ -1367,18 +1363,16 @@ def insert_needs_conflict_cte( return True for shape_el, _ in ir_stmt.subject.shape: - assert shape_el.rptr is not None - ptrref = shape_el.rptr.ptrref + ptrref = shape_el.expr.ptrref ptr_info = pg_types.get_ptrref_storage_info( ptrref, resolve_type=True, link_bias=False) # We need to generate a conflict CTE if we have a DML containing # pointer stored in the object itself - # XXX: if ( ptr_info.table_type == 'ObjectType' - and shape_el.old_expr - and irutils.contains_dml(shape_el.old_expr, skip_bindings=True) + and shape_el.expr.expr + and irutils.contains_dml(shape_el.expr.expr, skip_bindings=True) ): return True @@ -1657,7 +1651,7 @@ def process_update_body( # compile contents CTE elements = [ - (shape_el, not_none(shape_el.rptr).ptrref, shape_op) + (shape_el, shape_el.expr.ptrref, shape_op) for shape_el, shape_op in ir_stmt.subject.shape if shape_op != qlast.ShapeOp.MATERIALIZE ] @@ -1690,7 +1684,7 @@ def process_update_body( single_external = [ ir for ir, _ in external_updates - if ir.rptr and ir.rptr.dir_cardinality.is_single() + if ir.expr.dir_cardinality.is_single() ] rewrites = ir_stmt.rewrites and ir_stmt.rewrites.by_type.get(typeref) @@ -1839,9 +1833,10 @@ def process_update_rewrites( contents_select: pgast.SelectStmt, table_relation: pgast.RelRangeVar, range_relation: pgast.PathRangeVar, - single_external: List[irast.Set], + single_external: List[irast.SetE[irast.Pointer]], rewrites: irast.RewritesOfType, - elements: Sequence[Tuple[irast.Set, irast.BasePointerRef, qlast.ShapeOp]], + elements: Sequence[ + Tuple[irast.SetE[irast.Pointer], irast.BasePointerRef, qlast.ShapeOp]], ctx: context.CompilerContextLevel, ) -> tuple[ pgast.CommonTableExpr, @@ -1941,10 +1936,9 @@ def process_update_rewrites( # populate the field from the link overlays. handled = set(rewrites) for ext_ir in single_external: - assert ext_ir.rptr - handled.add(ext_ir.rptr.ptrref.shortname.name) + handled.add(ext_ir.expr.ptrref.shortname.name) actual_ptrref = irtyputils.find_actual_ptrref( - typeref, ext_ir.rptr.ptrref) + typeref, ext_ir.expr.ptrref) with rctx.subrel() as ectx: ext_rvar = relctx.new_pointer_rvar( ext_ir, link_bias=True, src_rvar=contents_rvar, @@ -2001,17 +1995,17 @@ def process_update_rewrites( def process_update_shape( ir_stmt: irast.UpdateStmt, rel: pgast.SelectStmt, - # XXX: Update type - elements: Sequence[Tuple[irast.Set, irast.BasePointerRef, qlast.ShapeOp]], + elements: Sequence[ + Tuple[irast.SetE[irast.Pointer], irast.BasePointerRef, qlast.ShapeOp]], typeref: irast.TypeRef, ctx: context.CompilerContextLevel, ) -> Tuple[ List[Tuple[pgast.ResTarget, irast.PathId]], - List[Tuple[irast.Set, qlast.ShapeOp]], + List[Tuple[irast.SetE[irast.Pointer], qlast.ShapeOp]], Dict[sn.Name, pgast.BaseExpr], ]: values: List[Tuple[pgast.ResTarget, irast.PathId]] = [] - external_updates: List[Tuple[irast.Set, qlast.ShapeOp]] = [] + external_updates: List[Tuple[irast.SetE[irast.Pointer], qlast.ShapeOp]] = [] ptr_map: Dict[sn.Name, pgast.BaseExpr] = {} for element, shape_ptrref, shape_op in elements: @@ -2264,7 +2258,7 @@ def check_update_type( def process_link_update( *, ir_stmt: irast.MutatingStmt, - ir_set: irast.Set, + ir_set: irast.SetE[irast.Pointer], shape_op: qlast.ShapeOp = qlast.ShapeOp.ASSIGN, source_typeref: irast.TypeRef, dml_cte: pgast.CommonTableExpr, @@ -2305,8 +2299,7 @@ def process_link_update( toplevel = ctx.toplevel_stmt is_insert = isinstance(ir_stmt, irast.InsertStmt) - rptr = ir_set.rptr - assert rptr is not None + rptr = ir_set.expr ptrref = rptr.ptrref assert isinstance(ptrref, irast.PointerRef) target_is_scalar = not irtyputils.is_object(ir_set.typeref) @@ -2668,13 +2661,13 @@ def process_link_update( # pgsql/delta.py. # Turn `foo := ` into just `foo`. - assert ir_set.rptr ptr_ref_set = irast.Set( path_id=ir_set.path_id, path_scope_id=ir_set.path_scope_id, typeref=ir_set.typeref, - expr=ir_set.rptr.replace(expr=None), + expr=ir_set.expr.replace(expr=None), ) + assert irutils.is_set_instance(ptr_ref_set, irast.Pointer) with ctx.new() as subctx: # TODO: Do we really need a copy here? things /seem/ @@ -2847,7 +2840,7 @@ def register_overlays( def process_link_values( *, ir_stmt: irast.MutatingStmt, - ir_expr: irast.Set, + ir_expr: irast.SetE[irast.Pointer], dml_rvar: pgast.PathRangeVar, dml_cte: pgast.CommonTableExpr, source_typeref: irast.TypeRef, @@ -2911,8 +2904,7 @@ def process_link_values( path_id=ir_stmt.subject.path_id, ctx=subrelctx) subrelctx.path_scope[ir_stmt.subject.path_id] = row_query - ir_rptr = ir_expr.rptr - assert ir_rptr is not None + ir_rptr = ir_expr.expr ptrref = ir_rptr.ptrref if ptrref.material_ptr is not None: ptrref = ptrref.material_ptr diff --git a/edb/pgsql/compiler/expr.py b/edb/pgsql/compiler/expr.py index 34c03409461..0f5810583da 100644 --- a/edb/pgsql/compiler/expr.py +++ b/edb/pgsql/compiler/expr.py @@ -736,7 +736,7 @@ def _compile_set( def _compile_shape( ir_set: irast.Set, - shape: Sequence[Tuple[irast.Set, qlast.ShapeOp]], + shape: Sequence[Tuple[irast.SetE[irast.Pointer], qlast.ShapeOp]], *, ctx: context.CompilerContextLevel) -> pgast.TupleVar: @@ -784,42 +784,9 @@ def _compile_set_in_singleton_mode( ctx: context.CompilerContextLevel) -> pgast.BaseExpr: if isinstance(node, irast.EmptySet): return pgast.NullConstant() - elif node.old_expr is not None: - return dispatch.compile(node.old_expr, ctx=ctx) else: - assert isinstance(node.expr, irast.Pointer) - ptrref = node.expr.ptrref - source = node.expr.source - - if isinstance(ptrref, irast.TupleIndirectionPointerRef): - tuple_val = dispatch.compile(source, ctx=ctx) - set_expr = astutils.tuple_getattr( - tuple_val, - source.typeref, - ptrref.shortname.name, - ) - return set_expr - - if ptrref.source_ptr is None and isinstance(source.expr, irast.Pointer): - raise errors.UnsupportedFeatureError( - 'unexpectedly long path in simple expr') - - # In most cases, we don't need to reference the rvar (since there - # will be only one in scope), but sometimes we do (for example NEW - # in trigger functions). - rvar_name = [] - if src := ctx.env.external_rvars.get((source.path_id, 'source')): - rvar_name = [src.alias.aliasname] - - # compile column name - ptr_stor_info = pg_types.get_ptrref_storage_info( - ptrref, resolve_type=False) - - colref = pgast.ColumnRef( - name=rvar_name + [ptr_stor_info.column_name], - nullable=node.expr.dir_cardinality.can_be_zero()) - - return colref + assert node.expr is not None + return dispatch.compile(node.expr, ctx=ctx) @dispatch.compile.register @@ -833,6 +800,53 @@ def compile_TypeRoot( return pgast.ColumnRef(name=name) +@dispatch.compile.register +def compile_Pointer( + rptr: irast.Pointer, *, ctx: context.CompilerContextLevel +) -> pgast.BaseExpr: + assert ctx.singleton_mode + + if rptr.expr: + return dispatch.compile(rptr.expr, ctx=ctx) + + ptrref = rptr.ptrref + source = rptr.source + + if ptrref.source_ptr is None and isinstance(source.expr, irast.Pointer): + raise errors.UnsupportedFeatureError( + 'unexpectedly long path in simple expr') + + # In most cases, we don't need to reference the rvar (since there + # will be only one in scope), but sometimes we do (for example NEW + # in trigger functions). + rvar_name = [] + if src := ctx.env.external_rvars.get((source.path_id, 'source')): + rvar_name = [src.alias.aliasname] + + # compile column name + ptr_stor_info = pg_types.get_ptrref_storage_info( + ptrref, resolve_type=False) + + colref = pgast.ColumnRef( + name=rvar_name + [ptr_stor_info.column_name], + nullable=rptr.dir_cardinality.can_be_zero()) + + return colref + + +@dispatch.compile.register +def compile_TupleIndirectionPointer( + rptr: irast.TupleIndirectionPointer, *, ctx: context.CompilerContextLevel +) -> pgast.BaseExpr: + tuple_val = dispatch.compile(rptr.source, ctx=ctx) + set_expr = astutils.tuple_getattr( + tuple_val, + rptr.source.typeref, + rptr.ptrref.shortname.name, + ) + return set_expr + + @dispatch.compile.register(irast.FTSDocument) def compile_FTSDocument( expr: irast.FTSDocument, *, ctx: context.CompilerContextLevel diff --git a/edb/pgsql/compiler/relctx.py b/edb/pgsql/compiler/relctx.py index 00e5d7d0940..c6bcbc707c1 100644 --- a/edb/pgsql/compiler/relctx.py +++ b/edb/pgsql/compiler/relctx.py @@ -40,7 +40,6 @@ from edb import errors -from edb.common.typeutils import not_none from edb.edgeql import qltypes from edb.edgeql import ast as qlast @@ -502,7 +501,7 @@ def new_primitive_rvar( ctx: context.CompilerContextLevel, ) -> pgast.PathRangeVar: # XXX: is this needed? - expr = ir_set.old_expr + expr = irutils.sub_expr(ir_set) if isinstance(expr, irast.TypeRoot): skip_subtypes = expr.skip_subtypes is_global = expr.is_cached_global @@ -522,11 +521,13 @@ def new_primitive_rvar( pathctx.put_rvar_path_bond(set_rvar, path_id) # FIXME: This feels like it should all not be here. - rptr = ir_set.rptr - if rptr is not None: - if (isinstance(rptr.ptrref, irast.TypeIntersectionPointerRef) - and rptr.source.rptr): - rptr = rptr.source.rptr + if isinstance(ir_set.expr, irast.Pointer): + rptr = ir_set.expr + if ( + isinstance(rptr.ptrref, irast.TypeIntersectionPointerRef) + and isinstance(rptr.source.expr, irast.Pointer) + ): + rptr = rptr.source.expr # If the set comes from an backlink, and the link is stored inline, # we want to output the source path. @@ -584,11 +585,13 @@ def new_root_rvar( def new_pointer_rvar( - ir_set: irast.Set, *, - link_bias: bool=False, - src_rvar: pgast.PathRangeVar, - ctx: context.CompilerContextLevel) -> pgast.PathRangeVar: - ir_ptr = not_none(ir_set.rptr) + ir_set: irast.SetE[irast.Pointer], + *, + link_bias: bool=False, + src_rvar: pgast.PathRangeVar, + ctx: context.CompilerContextLevel, +) -> pgast.PathRangeVar: + ir_ptr = ir_set.expr ptrref = ir_ptr.ptrref ptr_info = pg_types.get_ptrref_storage_info( @@ -604,12 +607,12 @@ def new_pointer_rvar( def _new_inline_pointer_rvar( - ir_set: irast.Set, *, + ir_set: irast.SetE[irast.Pointer], *, lateral: bool=True, ptr_info: pg_types.PointerStorageInfo, src_rvar: pgast.PathRangeVar, ctx: context.CompilerContextLevel) -> pgast.PathRangeVar: - ir_ptr = not_none(ir_set.rptr) + ir_ptr = ir_set.expr ptr_rel = pgast.SelectStmt() ptr_rvar = rvar_for_rel(ptr_rel, lateral=lateral, ctx=ctx) @@ -632,9 +635,9 @@ def _new_inline_pointer_rvar( def _new_mapped_pointer_rvar( - ir_set: irast.Set, *, + ir_set: irast.SetE[irast.Pointer], *, ctx: context.CompilerContextLevel) -> pgast.PathRangeVar: - ir_ptr = not_none(ir_set.rptr) + ir_ptr = ir_set.expr ptrref = ir_ptr.ptrref dml_source = irutils.get_dml_sources(ir_ptr.source) @@ -700,11 +703,10 @@ def new_rel_rvar( def semi_join( stmt: pgast.SelectStmt, - ir_set: irast.Set, src_rvar: pgast.PathRangeVar, *, + ir_set: irast.SetE[irast.Pointer], src_rvar: pgast.PathRangeVar, *, ctx: context.CompilerContextLevel) -> pgast.PathRangeVar: """Join an IR Set using semi-join.""" - rptr = ir_set.rptr - assert rptr is not None + rptr = ir_set.expr # Target set range. set_rvar = new_root_rvar(ir_set, lateral=True, ctx=ctx) @@ -1085,8 +1087,7 @@ def walk(ref: pgast.BaseExpr, path_id: irast.PathId, multi: bool) -> None: el_id = path_id.ptr_path().extend(ptrref=el_ptrref) - assert el.rptr - card = el.rptr.ptrref.dir_cardinality(el.rptr.direction) + card = el.expr.dir_cardinality is_singleton = card.is_single() and not card.can_be_zero() must_pack = not is_singleton @@ -2090,12 +2091,12 @@ def prep_filter(larg: pgast.SelectStmt, rarg: pgast.SelectStmt) -> None: def range_for_pointer( - ir_set: irast.Set, + ir_set: irast.SetE[irast.Pointer], *, dml_source: Sequence[irast.MutatingLikeStmt]=(), ctx: context.CompilerContextLevel, ) -> pgast.PathRangeVar: - pointer = not_none(ir_set.rptr) + pointer = ir_set.expr path_id = ir_set.path_id.ptr_path() external_rvar = ctx.env.external_rvars.get((path_id, 'source')) diff --git a/edb/pgsql/compiler/relgen.py b/edb/pgsql/compiler/relgen.py index 254d726a7a0..23addf8d699 100644 --- a/edb/pgsql/compiler/relgen.py +++ b/edb/pgsql/compiler/relgen.py @@ -586,18 +586,19 @@ def can_omit_optional_wrapper( ctx=ctx, ) - if isinstance(ir_set.rptr, irast.TupleIndirectionPointer): - return can_omit_optional_wrapper(ir_set.rptr.source, new_scope, ctx=ctx) + if isinstance(ir_set.expr, irast.TupleIndirectionPointer): + return can_omit_optional_wrapper(ir_set.expr.source, new_scope, ctx=ctx) return bool( - ir_set.old_expr is None # XXX + isinstance(ir_set.expr, irast.Pointer) + and (rptr := ir_set.expr) + and rptr.expr is None and not ir_set.path_id.is_objtype_path() and not ir_set.path_id.is_type_intersection_path() - and ir_set.rptr - and new_scope.is_visible(ir_set.rptr.source.path_id) - and not ir_set.rptr.is_inbound - and ir_set.rptr.ptrref.out_cardinality.is_single() - and not ir_set.rptr.ptrref.is_computable + and new_scope.is_visible(rptr.source.path_id) + and not rptr.is_inbound + and rptr.ptrref.out_cardinality.is_single() + and not rptr.ptrref.is_computable ) @@ -752,10 +753,13 @@ def finalize_optional_rel( def get_set_rel_alias(ir_set: irast.Set, *, ctx: context.CompilerContextLevel) -> str: dname = ir_set.path_id.target_name_hint.name - if ir_set.rptr is not None and ir_set.rptr.source.typeref is not None: + if ( + isinstance(ir_set.expr, irast.Pointer) + and ir_set.expr.source.typeref is not None + ): alias_hint = '{}_{}'.format( dname, - ir_set.rptr.ptrref.shortname.name + ir_set.expr.ptrref.shortname.name ) else: alias_hint = dname.replace('~', '-') @@ -800,13 +804,13 @@ def process_external_rel( def process_set_as_link_property_ref( - ir_set: irast.Set, *, ctx: context.CompilerContextLevel + ir_set: irast.SetE[irast.Pointer], *, ctx: context.CompilerContextLevel ) -> SetRVars: - assert ir_set.rptr is not None - ir_source = ir_set.rptr.source + rptr = ir_set.expr + ir_source = rptr.source rvars = [] - lpropref = ir_set.rptr.ptrref + lpropref = rptr.ptrref ptr_info = pg_types.get_ptrref_storage_info( lpropref, resolve_type=False, link_bias=False) @@ -850,7 +854,7 @@ def process_set_as_link_property_ref( if link_rvar is None: src_rvar = get_set_rvar(ir_source, ctx=newctx) - assert link_prefix.rptr is not None + assert irutils.is_set_instance(link_prefix, irast.Pointer) link_rvar = relctx.new_pointer_rvar( link_prefix, src_rvar=src_rvar, link_bias=True, ctx=newctx) @@ -923,13 +927,13 @@ def process_set_as_path_type_intersection( assert not rptr.expr, 'type intersection pointer with expr??' if (not source_is_visible - and ir_source.rptr is not None + and isinstance(ir_source.expr, irast.Pointer) and not ir_source.path_id.is_type_intersection_path() - and not ir_source.old_expr # XXX + and not ir_source.expr.expr and ( rptr.ptrref.is_subtype or pg_types.get_ptrref_storage_info( - ir_source.rptr.ptrref).table_type != 'ObjectType' + ir_source.expr.ptrref).table_type != 'ObjectType' )): # Otherwise, if the source link path is not visible, # and this is a subtype intersection, or the pointer is not inline, @@ -1026,11 +1030,11 @@ def _source_path_needs_semi_join( return False while ( - ir_source.rptr - and ir_source.rptr.dir_cardinality.is_single() - and not ir_source.rptr.expr + isinstance(ir_source.expr, irast.Pointer) + and ir_source.expr.dir_cardinality.is_single() + and not ir_source.expr.expr ): - ir_source = ir_source.rptr.source + ir_source = ir_source.expr.source if ctx.scope_tree.is_visible(ir_source.path_id): return False @@ -1077,7 +1081,8 @@ def process_set_as_path( ) main_rvar = None - source_rptr = ir_source.rptr + source_rptr = ( + ir_source.expr if isinstance(ir_source.expr, irast.Pointer) else None) if (irtyputils.is_id_ptrref(ptrref) and source_rptr is not None and isinstance(source_rptr.ptrref, irast.PointerRef) and not source_rptr.is_inbound @@ -1125,8 +1130,8 @@ def process_set_as_path( ['value', 'source'])) elif is_id_ref_to_inline_source: - assert ir_source.rptr is not None - ir_source = ir_source.rptr.source + assert source_rptr is not None + ir_source = source_rptr.source src_rvar = get_set_rvar(ir_source, ctx=ctx) elif not source_is_visible: @@ -1194,7 +1199,6 @@ def process_set_as_path( aspects = ['value', 'source'] src_rvar = get_set_rvar(ir_source, ctx=ctx) - assert ir_set.rptr is not None map_rvar = SetRVar( relctx.new_pointer_rvar(ir_set, src_rvar=src_rvar, ctx=ctx), path_id=ir_set.path_id.ptr_path(), @@ -2022,7 +2026,6 @@ def process_set_as_tuple_indirection( assert not rptr.expr, 'tuple indirection pointer with expr??' - assert not ir_set.old_expr with ctx.new() as subctx: # Usually the LHS is is not exposed, but when we are directly # projecting from an explicit tuple, and the result is a diff --git a/edb/pgsql/compiler/shapecomp.py b/edb/pgsql/compiler/shapecomp.py index a71974f8baf..e77b1fc4de1 100644 --- a/edb/pgsql/compiler/shapecomp.py +++ b/edb/pgsql/compiler/shapecomp.py @@ -42,7 +42,7 @@ def compile_shape( ir_set: irast.Set, - shape: Sequence[Tuple[irast.Set, qlast.ShapeOp]], *, + shape: Sequence[Tuple[irast.SetE[irast.Pointer], qlast.ShapeOp]], *, ctx: context.CompilerContextLevel) -> pgast.TupleVar: elements = [] @@ -81,7 +81,6 @@ def compile_shape( continue rptr = el.expr - assert isinstance(rptr, irast.Pointer) ptrref = rptr.ptrref # As an implementation expedient, we currently represent # AT_MOST_ONE materialized values with arrays diff --git a/edb/pgsql/schemamech.py b/edb/pgsql/schemamech.py index 7cd5fe2ccdd..1622c1e6bc2 100644 --- a/edb/pgsql/schemamech.py +++ b/edb/pgsql/schemamech.py @@ -174,7 +174,6 @@ def _edgeql_ref_to_pg_constr( if isinstance(tree, irast.Statement): tree = tree.expr - # XXX: or old_expr??? if isinstance(tree, irast.Set) and isinstance(tree.expr, irast.SelectStmt): tree = tree.expr.result @@ -346,7 +345,6 @@ def compile_constraint( options=origin_options, ) - # XXX: or old_expr??? assert origin_ir.expr.expr origin_terminal_refs = ir_utils.get_longest_paths( origin_ir.expr.expr @@ -706,7 +704,9 @@ def get_ref_storage_info( for ref in refs: ptr: s_pointers.PointerLike src: s_types.Type | s_pointers.PointerLike - if ref.rptr is None: + + rptr = ref.expr if isinstance(ref.expr, irast.Pointer) else None + if rptr is None: source_typeref = ref.typeref if not irtyputils.is_object(source_typeref): continue @@ -714,13 +714,14 @@ def get_ref_storage_info( assert isinstance(t, s_sources.Source) ptr = t.getptr(schema, s_name.UnqualName('id')) else: - ptrref = ref.rptr.ptrref + ptrref = rptr.ptrref schema, ptr = irtyputils.ptrcls_from_ptrref(ptrref, schema=schema) - source_typeref = ref.rptr.source.typeref + source_typeref = rptr.source.typeref if ptr.is_link_property(schema): - assert ref.rptr and ref.rptr.source and ref.rptr.source.rptr - srcref = ref.rptr.source.rptr.ptrref + assert rptr and rptr.source + assert isinstance(rptr.source.expr, irast.Pointer) + srcref = rptr.source.expr.ptrref schema, src = irtyputils.ptrcls_from_ptrref( srcref, schema=schema) if src.get_is_derived(schema): @@ -728,12 +729,12 @@ def get_ref_storage_info( # for the purposes of constraint expr compilation. src = src.get_bases(schema).first(schema) elif ptr.is_tuple_indirection(): - assert ref.rptr - refs.append(ref.rptr.source) + assert rptr + refs.append(rptr.source) continue elif ptr.is_type_intersection(): - assert ref.rptr - refs.append(ref.rptr.source) + assert rptr + refs.append(rptr.source) continue else: schema, src = irtyputils.ir_typeref_to_type(schema, source_typeref) diff --git a/edb/schema/constraints.py b/edb/schema/constraints.py index 74bb245d415..f93b5ad9d02 100644 --- a/edb/schema/constraints.py +++ b/edb/schema/constraints.py @@ -735,7 +735,7 @@ def _populate_concrete_constraint_attrs( args: Any = None, **kwargs: Any ) -> None: - from edb.ir import ast as ir_ast + from edb.ir import ast as irast from edb.ir import utils as ir_utils from . import pointers as s_pointers from . import links as s_links @@ -897,8 +897,8 @@ def _populate_concrete_constraint_attrs( has_any_multi = has_non_subject_multi = False for ref in refs: assert subject_obj - while ref.rptr: - rptr = ref.rptr + while isinstance(ref.expr, irast.Pointer): + rptr = ref.expr if rptr.dir_cardinality.is_multi(): has_any_multi = True @@ -908,7 +908,7 @@ def _populate_concrete_constraint_attrs( # in a constraint expression if it is itself a # singleton, regardless of other parts of the path.) if ( - isinstance(rptr.ptrref, ir_ast.PointerRef) + isinstance(rptr.ptrref, irast.PointerRef) and rptr.ptrref.id == subject_obj.id ): break @@ -917,9 +917,9 @@ def _populate_concrete_constraint_attrs( has_non_subject_multi = True if (not isinstance(rptr.ptrref, - ir_ast.TupleIndirectionPointerRef) + irast.TupleIndirectionPointerRef) and rptr.ptrref.source_ptr is None - and rptr.source.rptr is not None): + and isinstance(rptr.source.expr, irast.Pointer)): if isinstance(subject, s_links.Link): raise errors.InvalidConstraintDefinitionError( "link constraints may not access " diff --git a/edb/schema/pointers.py b/edb/schema/pointers.py index 29c75e06df8..cab6b72c622 100644 --- a/edb/schema/pointers.py +++ b/edb/schema/pointers.py @@ -1260,7 +1260,7 @@ def _parse_computable( orig_expr = irutils.unwrap_set(orig_expr) result_expr = orig_expr if isinstance(result_expr, irast.Set): - if result_expr.rptr is not None: + if isinstance(result_expr.expr, irast.Pointer): result_expr, _ = irutils.collapse_type_intersection( result_expr) @@ -1270,9 +1270,10 @@ def _parse_computable( computed_link_alias_is_backward = None if ( isinstance(result_expr, irast.Set) - and (expr_rptr := result_expr.rptr) is not None + and isinstance(result_expr.expr, irast.Pointer) + and (expr_rptr := result_expr.expr) and expr_rptr.direction is PointerDirection.Outbound - and expr_rptr.source.rptr is None + and not isinstance(expr_rptr.source.expr, irast.Pointer) and isinstance(expr_rptr.ptrref, irast.PointerRef) and schema.has_object(expr_rptr.ptrref.id) ): @@ -1297,14 +1298,14 @@ def _parse_computable( if ( computed_link_alias is None and isinstance(orig_expr, irast.Set) - and orig_expr.rptr + and isinstance(orig_expr.expr, irast.Pointer) and isinstance( - orig_expr.rptr.ptrref, irast.TypeIntersectionPointerRef) - and len(orig_expr.rptr.ptrref.rptr_specialization) == 1 + orig_expr.expr.ptrref, irast.TypeIntersectionPointerRef) + and len(orig_expr.expr.ptrref.rptr_specialization) == 1 and expr_rptr and expr_rptr.direction is not PointerDirection.Outbound ): - ptrref = list(orig_expr.rptr.ptrref.rptr_specialization)[0] + ptrref = list(orig_expr.expr.ptrref.rptr_specialization)[0] new_schema, aliased_ptr = irtyputils.ptrcls_from_ptrref( ptrref, schema=schema ) diff --git a/tests/test_edgeql_ir_card_inference.py b/tests/test_edgeql_ir_card_inference.py index c83c74b5cd6..2c1b98511bc 100644 --- a/tests/test_edgeql_ir_card_inference.py +++ b/tests/test_edgeql_ir_card_inference.py @@ -68,7 +68,7 @@ def run_test(self, *, source, spec, expected): shape = ir.expr.expr.result.shape for el, _ in shape: if str(el.path_id.rptr_name()).endswith(field): - card = el.rptr.ptrref.out_cardinality + card = el.expr.ptrref.out_cardinality self.assertEqual(card, expected_cardinality, 'unexpected cardinality:\n' + source) break From b8beb5f4f633f03b44d21e7cbf9d123e8bedf17a Mon Sep 17 00:00:00 2001 From: Elvis Pranskevichus Date: Tue, 26 Mar 2024 09:28:53 -0700 Subject: [PATCH 7/8] Make pyright shut up about schema field getters (#7119) Pyright has no idea about metaclass-generated getters for schema fields. --- pyproject.toml | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/pyproject.toml b/pyproject.toml index e539ed0c46e..8a84911095d 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -240,3 +240,7 @@ exclude = ["postgres", ".github"] lint.flake8-bugbear.extend-immutable-calls = [ "immutables.Map" ] + +[tool.pyright] +# Pyright has no idea about metaclass-generated getters for schema fields. +reportAttributeAccessIssue = false From b0e4022d45bd1659ce2ece6e367a9952631f30c8 Mon Sep 17 00:00:00 2001 From: "Michael J. Sullivan" Date: Tue, 26 Mar 2024 09:29:18 -0700 Subject: [PATCH 8/8] Make expr non-Optional in irast.Set (#7116) The bulk of the work to make this happen was done in #6938 (introducing TypeRoot) and #7090 (moving Pointer into expr). The final pieces are making EmptySet an Expr instead of a Set subclass and getting rid of the cases where we "clear" expressions out of a Set. The biggest benefit here is that the meaning of a Set is much clearer now, but there are some nice direct benefits too. This lets have fully table dispatched set compilation in several places now. `_get_set_rvar`, which was 155 lines long in 2.x, is now finally completely gone! `_compile_set_in_singleton_mode` is also gone, replaced with simply compiling the set's expression. Typechecker error messages involving SetE are also much nicer now. --- edb/edgeql/compiler/casts.py | 7 +- edb/edgeql/compiler/conflicts.py | 4 +- edb/edgeql/compiler/expr.py | 4 +- edb/edgeql/compiler/inference/cardinality.py | 15 +++- edb/edgeql/compiler/inference/multiplicity.py | 14 +++- edb/edgeql/compiler/inference/volatility.py | 12 ++- edb/edgeql/compiler/setgen.py | 39 +++++----- edb/edgeql/compiler/stmt.py | 1 - edb/edgeql/compiler/stmtctx.py | 9 ++- edb/edgeql/compiler/typegen.py | 9 ++- edb/edgeql/compiler/viewgen.py | 8 +- edb/ir/ast.py | 26 +++++-- edb/ir/staeval.py | 6 +- edb/ir/utils.py | 1 - edb/pgsql/compiler/config.py | 2 +- edb/pgsql/compiler/expr.py | 17 ++--- edb/pgsql/compiler/relctx.py | 2 +- edb/pgsql/compiler/relgen.py | 74 +++++++++---------- 18 files changed, 147 insertions(+), 103 deletions(-) diff --git a/edb/edgeql/compiler/casts.py b/edb/edgeql/compiler/casts.py index 70ce470cecd..f86c21bb434 100644 --- a/edb/edgeql/compiler/casts.py +++ b/edb/edgeql/compiler/casts.py @@ -83,9 +83,12 @@ def compile_cast( '"any" types or abstract scalars.', span=span) - if isinstance(ir_expr, irast.EmptySet): + if ( + isinstance(ir_expr, irast.Set) + and isinstance(ir_expr.expr, irast.EmptySet) + ): # For the common case of casting an empty set, we simply - # generate a new EmptySet node of the requested type. + # generate a new empty set node of the requested type. return setgen.new_empty_set( stype=new_stype, alias=ir_expr.path_id.target_name_hint.name, diff --git a/edb/edgeql/compiler/conflicts.py b/edb/edgeql/compiler/conflicts.py index 33fec9e9ce5..8cb6b27eee9 100644 --- a/edb/edgeql/compiler/conflicts.py +++ b/edb/edgeql/compiler/conflicts.py @@ -424,7 +424,7 @@ def _compile_conflict_select( assert isinstance(select_ir, irast.Set) # If we have an empty set, remake it with the right type - if isinstance(select_ir, irast.EmptySet): + if isinstance(select_ir.expr, irast.EmptySet): select_ir = setgen.new_empty_set(stype=subject_typ, ctx=ctx) return select_ir, always_check, from_parent @@ -706,7 +706,7 @@ def _compile_inheritance_conflict_selects( constrs=p, obj_constrs=o, span=stmt.span, ctx=ctx) - if isinstance(select_ir, irast.EmptySet): + if isinstance(select_ir.expr, irast.EmptySet): continue cnstr_ref = irast.ConstraintRef(id=cnstr.id) clauses.append( diff --git a/edb/edgeql/compiler/expr.py b/edb/edgeql/compiler/expr.py index 28389e5b3de..219b0da4a33 100644 --- a/edb/edgeql/compiler/expr.py +++ b/edb/edgeql/compiler/expr.py @@ -473,7 +473,7 @@ def _compile_dml_ifelse( els: list[qlast.Expr] = [] - if not isinstance(irutils.unwrap_set(if_ir), irast.EmptySet): + if not isinstance(irutils.unwrap_set(if_ir).expr, irast.EmptySet): if_b = qlast.ForQuery( iterator_alias=ctx.aliases.get('_ifelse_true_dummy'), iterator=qlast.SelectQuery( @@ -484,7 +484,7 @@ def _compile_dml_ifelse( ) els.append(if_b) - if not isinstance(irutils.unwrap_set(else_ir), irast.EmptySet): + if not isinstance(irutils.unwrap_set(else_ir).expr, irast.EmptySet): else_b = qlast.ForQuery( iterator_alias=ctx.aliases.get('_ifelse_false_dummy'), iterator=qlast.SelectQuery( diff --git a/edb/edgeql/compiler/inference/cardinality.py b/edb/edgeql/compiler/inference/cardinality.py index 31b839cf5f0..39b3af27331 100644 --- a/edb/edgeql/compiler/inference/cardinality.py +++ b/edb/edgeql/compiler/inference/cardinality.py @@ -308,8 +308,7 @@ def __infer_empty_set( scope_tree: irast.ScopeTreeNode, ctx: inference_context.InfCtx, ) -> qltypes.Cardinality: - return _infer_set( - ir, scope_tree=scope_tree, ctx=ctx) + return AT_MOST_ONE @_infer_cardinality.register @@ -345,6 +344,16 @@ def __infer_type_root( return MANY +@_infer_cardinality.register +def __infer_cleared( + ir: irast.RefExpr, + *, + scope_tree: irast.ScopeTreeNode, + ctx: inference_context.InfCtx, +) -> qltypes.Cardinality: + return MANY + + def _infer_pointer_cardinality( *, ptrcls: s_pointers.Pointer, @@ -632,8 +641,6 @@ def _infer_set_inner( card = cartesian_cardinality((source_card, rptrref_card)) - elif isinstance(ir, irast.EmptySet): - card = AT_MOST_ONE elif sub_expr is not None: card = expr_card else: diff --git a/edb/edgeql/compiler/inference/multiplicity.py b/edb/edgeql/compiler/inference/multiplicity.py index 10fc2714906..18c7179b9a6 100644 --- a/edb/edgeql/compiler/inference/multiplicity.py +++ b/edb/edgeql/compiler/inference/multiplicity.py @@ -185,6 +185,16 @@ def __infer_type_root( return UNIQUE +@_infer_multiplicity.register +def __infer_cleared( + ir: irast.RefExpr, + *, + scope_tree: irast.ScopeTreeNode, + ctx: inf_ctx.InfCtx, +) -> inf_ctx.MultiplicityInfo: + return DUPLICATE + + def _infer_shape( ir: irast.Set, *, @@ -940,9 +950,7 @@ def infer_multiplicity( card = cardinality.infer_cardinality( ir, is_mutation=is_mutation, scope_tree=scope_tree, ctx=ctx) - if isinstance(ir, irast.EmptySet): - result = EMPTY - elif isinstance(ir, irast.Set): + if isinstance(ir, irast.Set): result = _infer_set( ir, is_mutation=is_mutation, scope_tree=scope_tree, ctx=ctx, ) diff --git a/edb/edgeql/compiler/inference/volatility.py b/edb/edgeql/compiler/inference/volatility.py index d85a2aa9772..7f3799e2588 100644 --- a/edb/edgeql/compiler/inference/volatility.py +++ b/edb/edgeql/compiler/inference/volatility.py @@ -144,6 +144,14 @@ def __infer_type_root( return STABLE +@_infer_volatility_inner.register +def __infer_cleared_expr( + ir: irast.RefExpr, + env: context.Environment, +) -> InferredVolatility: + return IMMUTABLE + + @_infer_volatility_inner.register def _infer_pointer( ir: irast.Pointer, @@ -184,10 +192,8 @@ def __infer_set( and ir.expr.source.path_id not in env.singletons ): vol = _max_volatility((vol, STABLE)) - elif ir.expr is not None: - vol = _infer_volatility(ir.expr, env) else: - vol = STABLE + vol = _infer_volatility(ir.expr, env) # Cache our best-known as to this point volatility, to prevent # infinite recursion. diff --git a/edb/edgeql/compiler/setgen.py b/edb/edgeql/compiler/setgen.py index 1cf3adc8e28..90f60acf7fc 100644 --- a/edb/edgeql/compiler/setgen.py +++ b/edb/edgeql/compiler/setgen.py @@ -90,6 +90,7 @@ def new_set( *, stype: s_types.Type, + expr: irast.Expr, ctx: context.ContextLevel, ircls: Type[irast.Set] = irast.Set, **kwargs: Any, @@ -102,7 +103,6 @@ def new_set( ignore_rewrites: bool = kwargs.get('ignore_rewrites', False) - expr: Optional[irast.Expr] = kwargs.get('expr', None) skip_subtypes = False if isinstance(expr, irast.TypeRoot): skip_subtypes = expr.skip_subtypes @@ -124,7 +124,7 @@ def new_set( policies.try_type_rewrite(stype, skip_subtypes=skip_subtypes, ctx=ctx) typeref = typegen.type_to_typeref(stype, env=ctx.env) - ir_set = ircls(typeref=typeref, **kwargs) + ir_set = ircls(typeref=typeref, expr=expr, **kwargs) ctx.env.set_types[ir_set] = stype return ir_set @@ -142,7 +142,11 @@ def new_empty_set( typeref = typegen.type_to_typeref(stype, env=ctx.env) path_id = pathctx.get_expression_path_id(stype, alias, ctx=ctx) - ir_set = irast.EmptySet(path_id=path_id, typeref=typeref) + ir_set = irast.Set( + path_id=path_id, + typeref=typeref, + expr=irast.EmptySet(typeref=typeref), + ) ctx.env.set_types[ir_set] = stype return ir_set @@ -172,7 +176,7 @@ def new_set_from_set( path_scope_id: Optional[int | KeepCurrentT]=KeepCurrent, path_id: Optional[irast.PathId]=None, stype: Optional[s_types.Type]=None, - expr: Optional[irast.Expr | KeepCurrentT]=KeepCurrent, + expr: irast.Expr | KeepCurrentT=KeepCurrent, span: Optional[qlast.Span]=None, is_binding: Optional[irast.BindingKind]=None, is_schema_alias: Optional[bool]=None, @@ -1025,17 +1029,15 @@ def extend_path( target = orig_ptrcls.get_far_endpoint(ctx.env.schema, direction) assert isinstance(target, s_types.Type) - target_set = new_set( - stype=target, path_id=path_id, span=span, ctx=ctx) - ptr = irast.Pointer( source=source_set, direction=direction, ptrref=typegen.ptr_to_ptrref(ptrcls, ctx=ctx), is_definition=False, ) + target_set = new_set( + stype=target, path_id=path_id, span=span, expr=ptr, ctx=ctx) - target_set.expr = ptr is_computable = _is_computable_ptr(ptrcls, direction, ctx=ctx) if not ignore_computable and is_computable: target_set = computable_ptr_set( @@ -1240,7 +1242,6 @@ def type_intersection_set( if result.stype == arg_type: return source_set - poly_set = new_set(stype=result.stype, span=span, ctx=ctx) rptr_specialization = [] if ( @@ -1301,13 +1302,17 @@ def type_intersection_set( typeref_cache=ctx.env.type_ref_cache, ) - poly_set.path_id = source_set.path_id.extend(ptrref=ptrref) - - poly_set.expr = irast.TypeIntersectionPointer( - source=source_set, - ptrref=downcast(irast.TypeIntersectionPointerRef, ptrref), - direction=s_pointers.PointerDirection.Outbound, - optional=optional, + poly_set = new_set( + stype=result.stype, + path_id=source_set.path_id.extend(ptrref=ptrref), + expr=irast.TypeIntersectionPointer( + source=source_set, + ptrref=downcast(irast.TypeIntersectionPointerRef, ptrref), + direction=s_pointers.PointerDirection.Outbound, + optional=optional, + ), + span=span, + ctx=ctx, ) return poly_set @@ -1431,7 +1436,7 @@ def ensure_set( if srcctx is not None: ir_set = new_set_from_set(ir_set, span=srcctx, ctx=ctx) - if (isinstance(ir_set, irast.EmptySet) + if (irutils.is_set_instance(ir_set, irast.EmptySet) and (stype is None or stype.is_any(ctx.env.schema)) and typehint is not None): typegen.amend_empty_set_type(ir_set, typehint, env=ctx.env) diff --git a/edb/edgeql/compiler/stmt.py b/edb/edgeql/compiler/stmt.py index 11c9295ab6d..af3b882939e 100644 --- a/edb/edgeql/compiler/stmt.py +++ b/edb/edgeql/compiler/stmt.py @@ -1276,7 +1276,6 @@ def process_with_block( had_materialized = True typ = setgen.get_set_type(binding, ctx=ctx) ctx.env.materialized_sets[typ] = edgeql_tree, reason - assert binding.expr setgen.maybe_materialize(typ, binding, ctx=ctx) else: diff --git a/edb/edgeql/compiler/stmtctx.py b/edb/edgeql/compiler/stmtctx.py index 0b2693230df..4a59901c144 100644 --- a/edb/edgeql/compiler/stmtctx.py +++ b/edb/edgeql/compiler/stmtctx.py @@ -255,10 +255,15 @@ def fini_expression( # Clear out exprs that we decided to omit from the IR for ir_set in exprs_to_clear: + new = ( + irast.MaterializedExpr(typeref=ir_set.typeref) + if ir_set.is_materialized_ref + else irast.VisibleBindingExpr(typeref=ir_set.typeref) + ) if isinstance(ir_set.expr, irast.Pointer): - ir_set.expr.expr = None + ir_set.expr.expr = new else: - ir_set.expr = None + ir_set.expr = new # Analyze GROUP statements to find aggregates that can be optimized group.infer_group_aggregates(all_exprs, ctx=ctx) diff --git a/edb/edgeql/compiler/typegen.py b/edb/edgeql/compiler/typegen.py index 63af01d02e1..c24fe3f2d2f 100644 --- a/edb/edgeql/compiler/typegen.py +++ b/edb/edgeql/compiler/typegen.py @@ -47,7 +47,7 @@ def amend_empty_set_type( - es: irast.EmptySet, + es: irast.SetE[irast.EmptySet], t: s_types.Type, env: context.Environment ) -> None: @@ -77,7 +77,10 @@ def infer_common_type( seen_coll = False for i, arg in enumerate(irs): - if isinstance(arg, irast.EmptySet) and env.set_types[arg] is None: + if ( + isinstance(arg.expr, irast.EmptySet) + and env.set_types[arg] is None + ): empties.append(i) continue @@ -129,7 +132,7 @@ def infer_common_type( for i in empties: amend_empty_set_type( - cast(irast.EmptySet, irs[i]), common_type, env) + cast(irast.SetE[irast.EmptySet], irs[i]), common_type, env) return common_type diff --git a/edb/edgeql/compiler/viewgen.py b/edb/edgeql/compiler/viewgen.py index 0031d4d3e53..51235c502bc 100644 --- a/edb/edgeql/compiler/viewgen.py +++ b/edb/edgeql/compiler/viewgen.py @@ -964,7 +964,7 @@ def get_anchors(stype: s_objtypes.ObjectType) -> RewriteAnchors: by_type[ty] = {} for element in rewrites_of_type.values(): target = element.target_set - assert target and target.expr + assert target ptrref = typegen.ptr_to_ptrref(element.ptrcls, ctx=ctx) actual_ptrref = irtypeutils.find_actual_ptrref(ty, ptrref) @@ -1241,9 +1241,10 @@ def prepare_rewrite_anchors( namespace=ctx.path_id_namespace, env=ctx.env, ) old_set = setgen.new_set( - stype=stype, path_id=old_path_id, ctx=ctx + stype=stype, path_id=old_path_id, ctx=ctx, + expr=irast.TriggerAnchor( + typeref=typegen.type_to_typeref(stype, env=ctx.env)), ) - old_set.expr = irast.TriggerAnchor(typeref=old_set.typeref) else: old_set = None @@ -1681,6 +1682,7 @@ def _normalize_view_ptr_expr( # when compiling. old_expr = irexpr.expr if isinstance(old_expr, irast.Pointer): + assert old_expr.expr irexpr.expr = old_expr.expr irexpr = dispatch.compile(cast_qlexpr, ctx=subctx) if isinstance(old_expr, irast.Pointer): diff --git a/edb/ir/ast.py b/edb/ir/ast.py index 45d37cf6f02..26085ad8196 100644 --- a/edb/ir/ast.py +++ b/edb/ir/ast.py @@ -530,14 +530,28 @@ class TypeRoot(Expr): skip_subtypes: bool = False -T_co = typing.TypeVar('T_co', covariant=True, bound=typing.Optional[Expr]) +class RefExpr(Expr): + '''Different expressions sorts that refer to some kind of binding.''' + __abstract_node__ = True + typeref: TypeRef + + +class MaterializedExpr(RefExpr): + pass + + +class VisibleBindingExpr(RefExpr): + pass + + +T_expr_co = typing.TypeVar('T_expr_co', covariant=True, bound=Expr) # SetE is the base 'Set' type, and it is parameterized over what kind # of expression it holds. Most code uses the Set alias below, which -# instantiates it with Optional[Expr]. +# instantiates it with Expr. # irutils.is_set_instance can be used to refine the type. -class SetE(Base, typing.Generic[T_co]): +class SetE(Base, typing.Generic[T_expr_co]): '''A somewhat overloaded metadata container for expressions. Its primary purpose is to be the holder for expression metadata @@ -553,7 +567,7 @@ class SetE(Base, typing.Generic[T_co]): path_id: PathId path_scope_id: typing.Optional[int] = None typeref: TypeRef - expr: T_co = None # type: ignore + expr: T_expr_co shape: typing.Tuple[typing.Tuple[SetE[Pointer], qlast.ShapeOp], ...] = () anchor: typing.Optional[str] = None @@ -587,7 +601,7 @@ def __repr__(self) -> str: SetE.__name__ = 'Set' if typing.TYPE_CHECKING: - Set = SetE[typing.Optional[Expr]] + Set = SetE[Expr] else: Set = SetE @@ -792,7 +806,7 @@ class ConstExpr(Expr): typeref: TypeRef -class EmptySet(Set, ConstExpr): +class EmptySet(ConstExpr): pass diff --git a/edb/ir/staeval.py b/edb/ir/staeval.py index bf75f012c28..511fa75b74f 100644 --- a/edb/ir/staeval.py +++ b/edb/ir/staeval.py @@ -136,11 +136,7 @@ def evaluate_EmptySet( def evaluate_Set( ir_set: irast.Set, schema: s_schema.Schema) -> EvaluationResult: - if ir_set.expr is not None: - return evaluate(ir_set.expr, schema=schema) - else: - raise UnsupportedExpressionError( - 'expression is not constant', span=ir_set.span) + return evaluate(ir_set.expr, schema=schema) @evaluate.register diff --git a/edb/ir/utils.py b/edb/ir/utils.py index fd3e49bed77..7a1248f99a7 100644 --- a/edb/ir/utils.py +++ b/edb/ir/utils.py @@ -127,7 +127,6 @@ def is_empty(ir: irast.Base) -> bool: (isinstance(ir, irast.Array) and not ir.elements) or ( isinstance(ir, irast.Set) - and ir.expr is not None and is_empty(ir.expr) ) ) diff --git a/edb/pgsql/compiler/config.py b/edb/pgsql/compiler/config.py index 8eb655b1bbc..24313923e55 100644 --- a/edb/pgsql/compiler/config.py +++ b/edb/pgsql/compiler/config.py @@ -631,7 +631,7 @@ def _compile_config_value( output_format = context.OutputFormat.JSONB with context.output_format(ctx, output_format): - if isinstance(expr, irast.EmptySet): + if isinstance(expr.expr, irast.EmptySet): # Special handling for empty sets, because we want a # singleton representation of the value and not an empty rel # in this context. diff --git a/edb/pgsql/compiler/expr.py b/edb/pgsql/compiler/expr.py index 0f5810583da..7ef39aa1aec 100644 --- a/edb/pgsql/compiler/expr.py +++ b/edb/pgsql/compiler/expr.py @@ -53,7 +53,7 @@ def compile_Set( ctx: context.CompilerContextLevel) -> pgast.BaseExpr: if ctx.singleton_mode: - return _compile_set_in_singleton_mode(ir_set, ctx=ctx) + return dispatch.compile(ir_set.expr, ctx=ctx) is_toplevel = ctx.toplevel_stmt is context.NO_STMT @@ -80,7 +80,7 @@ def visit_Set( ctx: context.CompilerContextLevel) -> None: if ctx.singleton_mode: - _compile_set_in_singleton_mode(ir_set, ctx=ctx) + dispatch.compile(ir_set.expr, ctx=ctx) _compile_set_impl(ir_set, ctx=ctx) @@ -779,14 +779,11 @@ def _compile_shape( return result -def _compile_set_in_singleton_mode( - node: irast.Set, *, - ctx: context.CompilerContextLevel) -> pgast.BaseExpr: - if isinstance(node, irast.EmptySet): - return pgast.NullConstant() - else: - assert node.expr is not None - return dispatch.compile(node.expr, ctx=ctx) +@dispatch.compile.register +def compile_EmptySet( + expr: irast.EmptySet, *, ctx: context.CompilerContextLevel +) -> pgast.BaseExpr: + return pgast.NullConstant() @dispatch.compile.register diff --git a/edb/pgsql/compiler/relctx.py b/edb/pgsql/compiler/relctx.py index c6bcbc707c1..5aeb72f305f 100644 --- a/edb/pgsql/compiler/relctx.py +++ b/edb/pgsql/compiler/relctx.py @@ -423,7 +423,7 @@ def maybe_get_path_var( def new_empty_rvar( - ir_set: irast.EmptySet, *, + ir_set: irast.SetE[irast.EmptySet], *, ctx: context.CompilerContextLevel) -> pgast.PathRangeVar: nullrel = pgast.NullRelation( path_id=ir_set.path_id, type_or_ptr_ref=ir_set.typeref) diff --git a/edb/pgsql/compiler/relgen.py b/edb/pgsql/compiler/relgen.py index 23addf8d699..a67d7fa834f 100644 --- a/edb/pgsql/compiler/relgen.py +++ b/edb/pgsql/compiler/relgen.py @@ -34,6 +34,7 @@ Generic, TypeVar, Type, + cast, ) import contextlib @@ -204,7 +205,7 @@ def get_set_rvar( # about it later. subctx.pending_query = stmt - is_empty_set = isinstance(ir_set, irast.EmptySet) + is_empty_set = isinstance(ir_set.expr, irast.EmptySet) path_scope = relctx.get_scope(ir_set, ctx=subctx) new_scope = path_scope or subctx.scope_tree @@ -234,7 +235,7 @@ def get_set_rvar( relctx.update_scope(ir_set, stmt, ctx=subctx) # Actually compile the set - rvars = _get_set_rvar(ir_set, ctx=subctx) + rvars = _get_expr_set_rvar(ir_set.expr, ir_set, ctx=subctx) relctx.update_scope_masks(ir_set, rvars.main.rvar, ctx=subctx) if ctx.env.expand_inhviews: @@ -300,8 +301,8 @@ def _process_toplevel_query( ) -> pgast.PathRangeVar: relctx.init_toplevel_query(ir_set, ctx=ctx) - rvars = _get_set_rvar(ir_set, ctx=ctx) - if isinstance(ir_set, irast.EmptySet): + rvars = _get_expr_set_rvar(ir_set.expr, ir_set, ctx=ctx) + if isinstance(ir_set.expr, irast.EmptySet): # In cases where the top-level expression is an empty set # as opposed to a Set wrapping some expression or path, make # sure the generated empty rel gets selected in the toplevel @@ -380,20 +381,20 @@ def _get_expr_set_rvar( raise NotImplementedError(f'no relgen handler for {ir.__class__}') -T = TypeVar('T', contravariant=True, bound=Optional[irast.Expr]) +T_expr = TypeVar('T_expr', contravariant=True, bound=irast.Expr) -class _GetExprRvarFunc(Protocol, Generic[T]): +class _GetExprRvarFunc(Protocol, Generic[T_expr]): def __call__( - self, __ir_set: irast.SetE[T], *, ctx: context.CompilerContextLevel + self, __ir_set: irast.SetE[T_expr], *, ctx: context.CompilerContextLevel ) -> SetRVars: pass def register_get_rvar( - typ: Type[T], -) -> Callable[[_GetExprRvarFunc[T]], _GetExprRvarFunc[T]]: - def func(f: _GetExprRvarFunc[T]) -> _GetExprRvarFunc[T]: + typ: Type[T_expr], +) -> Callable[[_GetExprRvarFunc[T_expr]], _GetExprRvarFunc[T_expr]]: + def func(f: _GetExprRvarFunc[T_expr]) -> _GetExprRvarFunc[T_expr]: _get_expr_set_rvar.register(typ)( lambda _, ir, *, ctx: f(ir, ctx=ctx)) return f @@ -401,28 +402,6 @@ def func(f: _GetExprRvarFunc[T]) -> _GetExprRvarFunc[T]: return func -def _get_set_rvar( - ir_set: irast.Set, - *, - ctx: context.CompilerContextLevel, -) -> SetRVars: - # TODO: Turn *all* of these into expr fields we can dispatch on too. - - if ir_set.is_materialized_ref: - # Sets that are materialized_refs get initial processing like - # a subquery, but might be missing the expr. - return process_set_as_subquery(ir_set, ctx=ctx) - - if irutils.is_set_instance(ir_set, irast.Expr): - return _get_expr_set_rvar(ir_set.expr, ir_set, ctx=ctx) - - if isinstance(ir_set, irast.EmptySet): - # {} - return process_set_as_empty(ir_set, ctx=ctx) - - raise AssertionError(f'invalid Set! {ir_set}') - - def _get_source_rvar( ir_set: irast.Set, scope_stmt: pgast.SelectStmt, @@ -640,9 +619,14 @@ def prepare_optional_rel( with unionctx.subrel() as scopectx: emptyrel = scopectx.rel + empty_ir = irast.Set( + path_id=ir_set.path_id, + typeref=ir_set.typeref, + expr=irast.EmptySet(typeref=ir_set.typeref), + ) + emptyrvar = relctx.new_empty_rvar( - irast.EmptySet(path_id=ir_set.path_id, - typeref=ir_set.typeref), + cast('irast.SetE[irast.EmptySet]', empty_ir), ctx=scopectx) relctx.include_rvar( @@ -767,6 +751,7 @@ def get_set_rel_alias(ir_set: irast.Set, *, return alias_hint +# N.B: registered for get_rvar for TypeRoot below def process_set_as_root( ir_set: irast.Set, *, ctx: context.CompilerContextLevel ) -> SetRVars: @@ -776,7 +761,7 @@ def process_set_as_root( return process_external_rel(ir_set, ctx=ctx) assert not ir_set.is_visible_binding_ref, ( - f"Can't compile ref to visible binding {ir_set.path_id}" + f"Can't compile ref to visible binding root {ir_set.path_id}" ) rvar = relctx.new_root_rvar(ir_set, ctx=ctx) @@ -786,8 +771,19 @@ def process_set_as_root( register_get_rvar(irast.TypeRoot)(process_set_as_root) +@register_get_rvar(irast.VisibleBindingExpr) +def process_set_as_visible_binding( + ir_set: irast.SetE[irast.VisibleBindingExpr], + *, ctx: context.CompilerContextLevel +) -> SetRVars: + raise AssertionError( + f"Can't compile ref to visible binding {ir_set.path_id}" + ) + + +@register_get_rvar(irast.EmptySet) def process_set_as_empty( - ir_set: irast.EmptySet, *, ctx: context.CompilerContextLevel + ir_set: irast.SetE[irast.EmptySet], *, ctx: context.CompilerContextLevel ) -> SetRVars: rvar = relctx.new_empty_rvar(ir_set, ctx=ctx) @@ -1310,6 +1306,10 @@ def _lookup_set_rvar_in_source( return None +# N.B: registered for get_rvar for Stmt and MaterializedExpr below +# Also, called explicitly for Pointer when expr is not None +# TODO: This is a tangled mess that handles several cases. +# Most of the code is for computed pointers. def process_set_as_subquery( ir_set: irast.Set, *, ctx: context.CompilerContextLevel ) -> SetRVars: @@ -1495,6 +1495,7 @@ def process_set_as_subquery( register_get_rvar(irast.Stmt)(process_set_as_subquery) +register_get_rvar(irast.MaterializedExpr)(process_set_as_subquery) @_special_case('std::IN') @@ -2186,7 +2187,6 @@ def process_set_as_expr( ) -> SetRVars: with ctx.new() as newctx: newctx.expr_exposed = False - assert ir_set.expr is not None set_expr = dispatch.compile(ir_set.expr, ctx=newctx) pathctx.put_path_value_var_if_not_exists(ctx.rel, ir_set.path_id, set_expr)