def compile_path(expr: qlast.Path, *, ctx: context.ContextLevel) -> irast.Set: """Create an ir.Set representing the given EdgeQL path expression.""" anchors = ctx.anchors path_tip = None if expr.partial: if ctx.partial_path_prefix is not None: path_tip = ctx.partial_path_prefix else: raise errors.QueryError('could not resolve partial path ', context=expr.context) extra_scopes = {} computables = [] path_sets = [] for i, step in enumerate(expr.steps): if isinstance(step, qlast.Source): # 'self' can only appear as the starting path label # syntactically and is a known anchor try: path_tip = anchors[step.__class__] except KeyError: path_tip = anchors['__source__'] elif isinstance(step, qlast.Subject): # '__subject__' can only appear as the starting path label # syntactically and is a known anchor try: path_tip = anchors[step.__class__] except KeyError: path_tip = anchors['__subject__'] elif isinstance(step, qlast.ObjectRef): if i > 0: # pragma: no cover raise RuntimeError( 'unexpected ObjectRef as a non-first path item') refnode = None if not step.module and step.name not in ctx.aliased_views: # Check if the starting path label is a known anchor refnode = anchors.get(step.name) if refnode is not None: path_tip = new_set_from_set(refnode, preserve_scope_ns=True, ctx=ctx) else: stype = schemactx.get_schema_type( step, item_types=(s_objtypes.ObjectType, ), ctx=ctx) if (stype.get_view_type(ctx.env.schema) is not None and stype.get_name(ctx.env.schema) not in ctx.view_nodes): # This is a schema-level view, as opposed to # a WITH-block or inline alias view. stype = stmtctx.declare_view_from_schema(stype, ctx=ctx) view_set = ctx.view_sets.get(stype) if view_set is not None: path_tip = new_set_from_set(view_set, ctx=ctx) path_scope = ctx.path_scope_map.get(view_set) extra_scopes[path_tip] = path_scope.copy() else: path_tip = class_set(stype, ctx=ctx) view_scls = ctx.class_view_overrides.get(stype.id) if (view_scls is not None and view_scls != get_set_type(path_tip, ctx=ctx)): path_tip = ensure_set(path_tip, type_override=view_scls, ctx=ctx) elif isinstance(step, qlast.Ptr): # Pointer traversal step ptr_expr = step direction = (ptr_expr.direction or s_pointers.PointerDirection.Outbound) ptr_name = ptr_expr.ptr.name if ptr_expr.type == 'property': # Link property reference; the source is the # link immediately preceding this step in the path. source = irtyputils.ptrcls_from_ptrref(path_tip.rptr.ptrref, schema=ctx.env.schema) else: source = get_set_type(path_tip, ctx=ctx) with ctx.newscope(fenced=True, temporary=True) as subctx: if isinstance(source, s_abc.Tuple): path_tip = tuple_indirection_set( path_tip, source=source, ptr_name=ptr_name, source_context=step.context, ctx=subctx) else: path_tip = ptr_step_set(path_tip, source=source, ptr_name=ptr_name, direction=direction, ignore_computable=True, source_context=step.context, ctx=subctx) ptrcls = irtyputils.ptrcls_from_ptrref( path_tip.rptr.ptrref, schema=ctx.env.schema) if _is_computable_ptr(ptrcls, ctx=ctx): computables.append(path_tip) elif isinstance(step, qlast.TypeIndirection): arg_type = inference.infer_type(path_tip, ctx.env) if not isinstance(arg_type, s_objtypes.ObjectType): raise errors.QueryError( f'invalid type filter operand: ' f'{arg_type.get_displayname(ctx.env.schema)} ' f'is not an object type', context=step.context) typ = schemactx.get_schema_type(step.type.maintype, ctx=ctx) if not isinstance(typ, s_objtypes.ObjectType): raise errors.QueryError( f'invalid type filter operand: ' f'{typ.get_displayname(ctx.env.schema)} is not ' f'an object type', context=step.type.context) # The expression already of the desired type, elide # the indirection. if arg_type != typ: path_tip = class_indirection_set(path_tip, typ, optional=False, ctx=ctx) else: # Arbitrary expression if i > 0: # pragma: no cover raise RuntimeError( 'unexpected expression as a non-first path item') with ctx.newscope(fenced=True, temporary=True) as subctx: path_tip = ensure_set(dispatch.compile(step, ctx=subctx), ctx=subctx) if path_tip.path_id.is_type_indirection_path(): scope_set = path_tip.rptr.source else: scope_set = path_tip extra_scopes[scope_set] = subctx.path_scope for key_path_id in path_tip.path_id.iter_weak_namespace_prefixes(): mapped = ctx.view_map.get(key_path_id) if mapped is not None: path_tip = new_set(path_id=mapped.path_id, stype=get_set_type(path_tip, ctx=ctx), expr=mapped.expr, rptr=mapped.rptr, ctx=ctx) break if pathctx.path_is_banned(path_tip.path_id, ctx=ctx): dname = stype.get_displayname(ctx.env.schema) raise errors.QueryError( f'invalid reference to {dname}: ' f'self-referencing INSERTs are not allowed', hint=(f'Use DETACHED if you meant to refer to an ' f'uncorrelated {dname} set'), context=step.context, ) path_sets.append(path_tip) path_tip.context = expr.context pathctx.register_set_in_scope(path_tip, ctx=ctx) for ir_set in computables: scope = ctx.path_scope.find_descendant(ir_set.path_id) if scope is None: # The path is already in the scope, no point # in recompiling the computable expression. continue with ctx.new() as subctx: subctx.path_scope = scope comp_ir_set = computable_ptr_set(ir_set.rptr, ctx=subctx) i = path_sets.index(ir_set) if i != len(path_sets) - 1: path_sets[i + 1].rptr.source = comp_ir_set else: path_tip = comp_ir_set path_sets[i] = comp_ir_set for ir_set, scope in extra_scopes.items(): node = ctx.path_scope.find_descendant(ir_set.path_id) if node is None: # The path portion not being a descendant means # that is is already present in the scope above us, # along with the view scope. continue fuse_scope_branch(ir_set, node, scope, ctx=ctx) if ir_set.path_scope_id is None: pathctx.assign_set_scope(ir_set, node, ctx=ctx) return path_tip
def computable_ptr_set(rptr: irast.Pointer, *, unnest_fence: bool = False, same_computable_scope: bool = False, ctx: context.ContextLevel) -> irast.Set: """Return ir.Set for a pointer defined as a computable.""" ptrcls = irtyputils.ptrcls_from_ptrref(rptr.ptrref, schema=ctx.env.schema) source_set = rptr.source source_scls = get_set_type(source_set, ctx=ctx) # process_view() may generate computable pointer expressions # in the form "self.linkname". To prevent infinite recursion, # self must resolve to the parent type of the view NOT the view # type itself. Similarly, when resolving computable link properties # make sure that we use ptrcls.derived_from. if source_scls.is_view(ctx.env.schema): source_set_stype = source_scls.peel_view(ctx.env.schema) source_set = new_set_from_set(source_set, stype=source_set_stype, preserve_scope_ns=True, ctx=ctx) source_set.shape = [] if source_set.rptr is not None: schema = ctx.env.schema source_rptrref = source_set.rptr.ptrref source_rptrcls = irtyputils.ptrcls_from_ptrref(source_rptrref, schema=schema) derived_from = source_rptrcls.get_derived_from(schema) if (derived_from is not None and not derived_from.generic(schema) and derived_from.get_derived_from(schema) is not None and ptrcls.is_link_property(schema)): source_set.rptr.ptrref = irtyputils.ptrref_from_ptrcls( source_ref=source_rptrref.dir_source, target_ref=source_rptrref.dir_target, direction=source_rptrref.direction, parent_ptr=source_rptrref.parent_ptr, ptrcls=derived_from, schema=schema, ) stmtctx.ensure_ptrref_cardinality(derived_from, source_set.rptr.ptrref, ctx=ctx) try: qlexpr, qlctx, inner_source_path_id, path_id_ns = \ ctx.source_map[ptrcls] except KeyError: ptrcls_default = ptrcls.get_default(ctx.env.schema) if not ptrcls_default: ptrcls_sn = ptrcls.get_shortname(ctx.env.schema) raise ValueError(f'{ptrcls_sn!r} is not a computable pointer') qlexpr = qlparser.parse(ptrcls_default.text) # NOTE: Validation of the expression type is not the concern # of this function. For any non-object pointer target type, # the default expression must be assignment-cast into that # type. target_scls = ptrcls.get_target(ctx.env.schema) if not target_scls.is_object_type(): qlexpr = qlast.TypeCast( type=astutils.type_to_ql_typeref(target_scls, schema=ctx.env.schema), expr=qlexpr, ) qlexpr = astutils.ensure_qlstmt(qlexpr) qlctx = None inner_source_path_id = None path_id_ns = None if qlctx is None: # Schema-level computable, completely detached context newctx = ctx.detached else: newctx = _get_computable_ctx(rptr=rptr, source=source_set, source_scls=source_scls, inner_source_path_id=inner_source_path_id, path_id_ns=path_id_ns, same_scope=same_computable_scope, qlctx=qlctx, ctx=ctx) if ptrcls.is_link_property(ctx.env.schema): source_path_id = rptr.source.path_id.ptr_path() else: source_path_id = rptr.target.path_id.src_path() result_stype = ptrcls.get_target(ctx.env.schema) result_path_id = pathctx.extend_path_id( source_path_id, ptrcls=ptrcls, direction=s_pointers.PointerDirection.Outbound, target=result_stype, ns=ctx.path_id_namespace, ctx=ctx) with newctx() as subctx: subctx.view_scls = result_stype subctx.view_rptr = context.ViewRPtr(source_scls, ptrcls=ptrcls, rptr=rptr) subctx.anchors[qlast.Source] = source_set subctx.empty_result_type_hint = ptrcls.get_target(ctx.env.schema) subctx.partial_path_prefix = source_set if isinstance(qlexpr, qlast.Statement) and unnest_fence: subctx.stmt_metadata[qlexpr] = context.StatementMetadata( is_unnest_fence=True) comp_ir_set = dispatch.compile(qlexpr, ctx=subctx) comp_ir_set_copy = new_set_from_set(comp_ir_set, ctx=ctx) pending_cardinality = ctx.pending_cardinality.get(ptrcls) if pending_cardinality is not None and not pending_cardinality.from_parent: stmtctx.get_pointer_cardinality_later( ptrcls=ptrcls, irexpr=comp_ir_set_copy, specified_card=pending_cardinality.specified_cardinality, source_ctx=pending_cardinality.source_ctx, ctx=ctx) def _check_cardinality(ctx): if ptrcls.singular(ctx.env.schema): stmtctx.enforce_singleton_now(comp_ir_set_copy, ctx=ctx) stmtctx.at_stmt_fini(_check_cardinality, ctx=ctx) comp_ir_set = new_set_from_set(comp_ir_set, path_id=result_path_id, rptr=rptr, ctx=ctx) rptr.target = comp_ir_set return comp_ir_set
def compile_insert_unless_conflict_on( stmt: irast.InsertStmt, insert_subject: qlast.Path, constraint_spec: qlast.Expr, else_branch: Optional[qlast.Expr], *, ctx: context.ContextLevel, ) -> irast.OnConflictClause: with ctx.new() as constraint_ctx: constraint_ctx.partial_path_prefix = stmt.subject # We compile the name here so we can analyze it, but we don't do # anything else with it. cspec_res = setgen.ensure_set(dispatch.compile( constraint_spec, ctx=constraint_ctx), ctx=constraint_ctx) if not cspec_res.rptr: raise errors.QueryError( 'UNLESS CONFLICT argument must be a property', context=constraint_spec.context, ) if cspec_res.rptr.source.path_id != stmt.subject.path_id: raise errors.QueryError( 'UNLESS CONFLICT argument must be a property of the ' 'type being inserted', context=constraint_spec.context, ) schema = ctx.env.schema schema, typ = typeutils.ir_typeref_to_type(schema, stmt.subject.typeref) assert isinstance(typ, s_objtypes.ObjectType) real_typ = typ.get_nearest_non_derived_parent(schema) schema, ptr = ( typeutils.ptrcls_from_ptrref(cspec_res.rptr.ptrref, schema=schema)) if not isinstance(ptr, s_pointers.Pointer): raise errors.QueryError( 'UNLESS CONFLICT property must be a property', context=constraint_spec.context, ) ptr = ptr.get_nearest_non_derived_parent(schema) ptr_card = ptr.get_cardinality(schema) if not ptr_card.is_single(): raise errors.QueryError( 'UNLESS CONFLICT property must be a SINGLE property', context=constraint_spec.context, ) exclusive_constr = schema.get('std::exclusive', type=s_constr.Constraint) ex_cnstrs = [c for c in ptr.get_constraints(schema).objects(schema) if c.issubclass(schema, exclusive_constr)] if len(ex_cnstrs) != 1: raise errors.QueryError( 'UNLESS CONFLICT property must have a single exclusive constraint', context=constraint_spec.context, ) module_id = schema.get_global( s_mod.Module, ptr.get_name(schema).module).id field_name = cspec_res.rptr.ptrref.shortname ds = {field_name.name: (ptr, ex_cnstrs)} select_ir = compile_insert_unless_conflict_select( stmt, insert_subject, real_typ, constrs=ds, obj_constrs=[], parser_context=stmt.context, ctx=ctx) # Compile an else branch else_ir = None if else_branch: # The ELSE needs to be able to reference the subject in an # UPDATE, even though that would normally be prohibited. ctx.path_scope.factoring_allowlist.add(stmt.subject.path_id) # Compile else else_ir = dispatch.compile( astutils.ensure_qlstmt(else_branch), ctx=ctx) assert isinstance(else_ir, irast.Set) return irast.OnConflictClause( constraint=irast.ConstraintRef( id=ex_cnstrs[0].id, module_id=module_id), select_ir=select_ir, else_ir=else_ir )
def _get_ref_storage_info(cls, schema, refs): link_biased = {} objtype_biased = {} ref_ptrs = {} for ref in refs: rptr = ref.rptr if rptr is not None: ptrref = ref.rptr.ptrref ptr = irtyputils.ptrcls_from_ptrref(ptrref, schema=schema) if ptr.is_link_property(schema): srcref = ref.rptr.source.rptr.ptrref src = irtyputils.ptrcls_from_ptrref(srcref, schema=schema) if src.get_is_derived(schema): # This specialized pointer was derived specifically # for the purposes of constraint expr compilation. src = src.get_bases(schema).first(schema) else: src = irtyputils.ir_typeref_to_type( schema, ref.rptr.source.typeref) ref_ptrs[ref] = (ptr, src) for ref, (ptr, src) in ref_ptrs.items(): ptr_info = types.get_pointer_storage_info(ptr, source=src, resolve_type=False, schema=schema) # See if any of the refs are hosted in pointer tables and others # are not... if ptr_info.table_type == 'link': link_biased[ref] = ptr_info else: objtype_biased[ref] = ptr_info if link_biased and objtype_biased: break if link_biased and objtype_biased: for ref in objtype_biased.copy(): ptr, src = ref_ptrs[ref] ptr_info = types.get_pointer_storage_info(ptr, source=src, resolve_type=False, link_bias=True, schema=schema) if ptr_info.table_type == 'link': link_biased[ref] = ptr_info objtype_biased.pop(ref) ref_tables = {} for ref, ptr_info in itertools.chain(objtype_biased.items(), link_biased.items()): ptr, src = ref_ptrs[ref] try: ref_tables[ptr_info.table_name].append( (ref, ptr, src, ptr_info)) except KeyError: ref_tables[ptr_info.table_name] = [(ref, ptr, src, ptr_info)] return ref_tables
def _parse_computable( self, expr: qlast.Base, schema: s_schema.Schema, context: sd.CommandContext, ) -> Tuple[s_schema.Schema, s_types.Type, Optional[PointerLike]]: from edb.ir import ast as irast from edb.ir import typeutils as irtyputils from edb.schema import objtypes as s_objtypes # "source" attribute is set automatically as a refdict back-attr parent_ctx = self.get_referrer_context(context) assert parent_ctx is not None source_name = parent_ctx.op.classname source = schema.get(source_name, type=s_objtypes.ObjectType) ptr_name = self.get_displayname() expression = s_expr.Expression.compiled( s_expr.Expression.from_ast(expr, schema, context.modaliases), schema=schema, options=qlcompiler.CompilerOptions( modaliases=context.modaliases, anchors={qlast.Source().name: source}, path_prefix_anchor=qlast.Source().name, singletons=frozenset([source]), in_ddl_context_name=f'computable {ptr_name!r}', ), ) assert isinstance(expression.irast, irast.Statement) base = None target = expression.irast.stype result_expr = expression.irast.expr # Process a computable pointer which potentially could be an # aliased link that should inherit link properties. if (isinstance(result_expr, irast.Set) and result_expr.rptr is not None): expr_rptr = result_expr.rptr while isinstance(expr_rptr, irast.TypeIntersectionPointer): expr_rptr = expr_rptr.source.rptr is_ptr_alias = ( expr_rptr.direction is PointerDirection.Outbound ) if is_ptr_alias: new_schema, base = irtyputils.ptrcls_from_ptrref( expr_rptr.ptrref, schema=schema ) # Only pointers coming from the same source as the # alias should be "inherited" (in order to preserve # link props). Random paths coming from other sources # get treated same as any other arbitrary expression # in a computable. if base.get_source(new_schema) != source: base = None else: schema = new_schema self.set_attribute_value('expr', expression) required, card = expression.irast.cardinality.to_schema_value() is_alter = ( isinstance(self, sd.AlterObject) or ( isinstance(self, sd.AlterObjectFragment) and isinstance(self.get_parent_op(context), sd.AlterObject) ) ) spec_required = self.get_attribute_value('required') if spec_required is None and is_alter: spec_required = self.scls.get_required(schema) spec_card = self.get_attribute_value('cardinality') if spec_card is None and is_alter: spec_card = self.scls.get_cardinality(schema) if spec_required and not required: srcctx = self.get_attribute_source_context('target') raise errors.SchemaDefinitionError( f'possibly an empty set returned by an ' f'expression for the computable ' f'{ptr_name!r} ' f"declared as 'required'", context=srcctx ) if ( spec_card in {None, qltypes.SchemaCardinality.ONE} and card is not qltypes.SchemaCardinality.ONE ): srcctx = self.get_attribute_source_context('target') raise errors.SchemaDefinitionError( f'possibly more than one element returned by an ' f'expression for the computable ' f'{ptr_name!r} ' f"declared as 'single'", context=srcctx ) if spec_card is None: self.set_attribute_value('cardinality', card) if not spec_required: self.set_attribute_value('required', required) self.set_attribute_value('computable', True) return schema, target, base
def _get_shape_configuration( ir_set: irast.Set, *, rptr: typing.Optional[irast.Pointer]=None, parent_view_type: typing.Optional[s_types.ViewType]=None, ctx: context.ContextLevel) \ -> typing.List[typing.Tuple[irast.Set, s_pointers.Pointer]]: """Return a list of (source_set, ptrcls) pairs as a shape for a given set. """ stype = setgen.get_set_type(ir_set, ctx=ctx) sources = [] link_view = False is_objtype = ir_set.path_id.is_objtype_path() if rptr is None: rptr = ir_set.rptr if rptr is not None: rptrcls = irtyputils.ptrcls_from_ptrref( rptr.ptrref, schema=ctx.env.schema) else: rptrcls = None link_view = ( rptrcls is not None and not rptrcls.is_link_property(ctx.env.schema) and _link_has_shape(rptrcls, ctx=ctx) ) if is_objtype or not link_view: sources.append(stype) if link_view: sources.append(rptrcls) shape_ptrs = [] id_present_in_shape = False for source in sources: for ptr in ctx.env.view_shapes[source]: if (ptr.is_link_property(ctx.env.schema) and ir_set.path_id != rptr.target.path_id): path_tip = rptr.target else: path_tip = ir_set shape_ptrs.append((path_tip, ptr)) if source is stype and ptr.is_id_pointer(ctx.env.schema): id_present_in_shape = True if is_objtype and not id_present_in_shape: view_type = stype.get_view_type(ctx.env.schema) is_mutation = view_type in (s_types.ViewType.Insert, s_types.ViewType.Update) is_parent_update = parent_view_type is s_types.ViewType.Update implicit_id = ( # shape is not specified at all not shape_ptrs # implicit ids are always wanted 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.expr is not None) ) if implicit_id: # We want the id in this shape and it's not already there, # so insert it in the first position. pointers = stype.get_pointers(ctx.env.schema).objects( ctx.env.schema) for ptr in pointers: if ptr.is_id_pointer(ctx.env.schema): view_shape = ctx.env.view_shapes[stype] if ptr not in view_shape: shape_metadata = ctx.env.view_shapes_metadata[stype] view_shape.insert(0, ptr) shape_metadata.has_implicit_id = True shape_ptrs.insert(0, (ir_set, ptr)) break if (ir_set.typeref is not None and irtyputils.is_object(ir_set.typeref) and parent_view_type is not s_types.ViewType.Insert and parent_view_type is not s_types.ViewType.Update and ctx.implicit_tid_in_shapes): ql = qlast.ShapeElement( expr=qlast.Path( steps=[qlast.Ptr( ptr=qlast.ObjectRef(name='__tid__'), direction=s_pointers.PointerDirection.Outbound, )], ), compexpr=qlast.Path( steps=[ qlast.Source(), qlast.Ptr( ptr=qlast.ObjectRef(name='__type__'), direction=s_pointers.PointerDirection.Outbound, ), qlast.Ptr( ptr=qlast.ObjectRef(name='id'), direction=s_pointers.PointerDirection.Outbound, ) ] ) ) with ctx.newscope(fenced=True) as scopectx: scopectx.anchors = scopectx.anchors.copy() scopectx.anchors[qlast.Source] = ir_set ptr = _normalize_view_ptr_expr( ql, stype, path_id=ir_set.path_id, ctx=scopectx) view_shape = ctx.env.view_shapes[stype] if ptr not in view_shape: view_shape.insert(0, ptr) shape_ptrs.insert(0, (ir_set, ptr)) return shape_ptrs
def compile_insert_unless_conflict( stmt: irast.InsertStmt, insert_subject: qlast.Path, constraint_spec: qlast.Expr, else_branch: Optional[qlast.Expr], *, ctx: context.ContextLevel, ) -> irast.OnConflictClause: with ctx.new() as constraint_ctx: constraint_ctx.partial_path_prefix = stmt.subject # We compile the name here so we can analyze it, but we don't do # anything else with it. cspec_res = setgen.ensure_set(dispatch.compile(constraint_spec, ctx=constraint_ctx), ctx=constraint_ctx) if not cspec_res.rptr: raise errors.QueryError( 'ON CONFLICT argument must be a property', context=constraint_spec.context, ) if cspec_res.rptr.source.path_id != stmt.subject.path_id: raise errors.QueryError( 'ON CONFLICT argument must be a property of the ' 'type being inserted', context=constraint_spec.context, ) schema = ctx.env.schema schema, ptr = (typeutils.ptrcls_from_ptrref(cspec_res.rptr.ptrref, schema=schema)) if not isinstance(ptr, s_pointers.Pointer): raise errors.QueryError( 'ON CONFLICT property must be a property', context=constraint_spec.context, ) ptr = ptr.get_nearest_non_derived_parent(schema) if ptr.get_cardinality(schema) != qltypes.SchemaCardinality.ONE: raise errors.QueryError( 'ON CONFLICT property must be a SINGLE property', context=constraint_spec.context, ) exclusive_constr: s_constr.Constraint = schema.get('std::exclusive') ex_cnstrs = [ c for c in ptr.get_constraints(schema).objects(schema) if c.issubclass(schema, exclusive_constr) ] if len(ex_cnstrs) != 1: raise errors.QueryError( 'ON CONFLICT property must have a single exclusive constraint', context=constraint_spec.context, ) module_id = schema.get_global(s_mod.Module, ptr.get_name(schema).module).id field_name = cspec_res.rptr.ptrref.shortname # Find the IR corresponding to our field # FIXME: Is there a better way to do this? for elem, _ in stmt.subject.shape: if elem.rptr.ptrref.shortname == field_name: key = elem.expr break else: raise errors.QueryError( 'INSERT ON CONFLICT property requires matching shape', context=constraint_spec.context, ) # FIXME: This reuse of the source ctx.anchors = ctx.anchors.copy() source_alias = ctx.aliases.get('a') ctx.anchors[source_alias] = setgen.ensure_set(key, ctx=ctx) anchor = qlast.Path(steps=[qlast.ObjectRef(name=source_alias)]) ctx.env.schema = schema # Compile an else branch else_info = None if else_branch: # Produce a query that finds the conflicting objects nobe = qlast.SelectQuery( result=insert_subject, where=qlast.BinOp(op='=', left=constraint_spec, right=anchor), ) select_ir = dispatch.compile(nobe, ctx=ctx) select_ir = setgen.scoped_set(select_ir, force_reassign=True, ctx=ctx) assert isinstance(select_ir, irast.Set) # The ELSE needs to be able to reference the subject in an # UPDATE, even though that would normally be prohibited. ctx.path_scope.factoring_allowlist.add(stmt.subject.path_id) # Compile else else_ir = dispatch.compile(astutils.ensure_qlstmt(else_branch), ctx=ctx) assert isinstance(else_ir, irast.Set) else_info = irast.OnConflictElse(select_ir, else_ir) return irast.OnConflictClause( irast.ConstraintRef(id=ex_cnstrs[0].id, module_id=module_id), else_info)
def _parse_computable( self, expr: qlast.Base, schema: s_schema.Schema, context: sd.CommandContext, ) -> Tuple[s_schema.Schema, s_types.Type, Optional[PointerLike]]: from edb.ir import ast as irast from edb.ir import typeutils as irtyputils from edb.schema import objtypes as s_objtypes # "source" attribute is set automatically as a refdict back-attr parent_ctx = self.get_referrer_context(context) assert parent_ctx is not None source_name = parent_ctx.op.classname source = schema.get(source_name, type=s_objtypes.ObjectType) expression = s_expr.Expression.compiled( s_expr.Expression.from_ast(expr, schema, context.modaliases), schema=schema, options=qlcompiler.CompilerOptions( modaliases=context.modaliases, anchors={qlast.Source().name: source}, path_prefix_anchor=qlast.Source().name, singletons=frozenset([source]), ), ) assert isinstance(expression.irast, irast.Statement) base = None target = expression.irast.stype result_expr = expression.irast.expr.expr if (isinstance(result_expr, irast.SelectStmt) and result_expr.result.rptr is not None): expr_rptr = result_expr.result.rptr while isinstance(expr_rptr, irast.TypeIntersectionPointer): expr_rptr = expr_rptr.source.rptr is_ptr_alias = (expr_rptr.direction is PointerDirection.Outbound) if is_ptr_alias: schema, base = irtyputils.ptrcls_from_ptrref(expr_rptr.ptrref, schema=schema) self.set_attribute_value('expr', expression) required, card = expression.irast.cardinality.to_schema_value() spec_required = self.get_attribute_value('required') spec_card = self.get_attribute_value('cardinality') # If cardinality was unspecified and the computable is not # required, use the inferred cardinality. if spec_card is None and not spec_required: self.set_attribute_value('required', required) self.set_attribute_value('cardinality', card) else: # Otherwise honor the spec, so no cardinality change, but check # that it's valid. if spec_card is None: # A computable link is marked explicitly as # "required", so we assume that omitted cardinality is # "single". Basically, to infer the cardinality both # cardinality-related qualifiers need to be omitted. spec_card = qltypes.SchemaCardinality.ONE if spec_required and not required: ptr_name = sn.shortname_from_fullname( self.get_attribute_value('name')).name srcctx = self.get_attribute_source_context('target') raise errors.QueryError( f'possibly an empty set returned by an ' f'expression for a computable ' f'{ptr_name!r} ' f"declared as 'required'", context=srcctx) if (spec_card is qltypes.SchemaCardinality.ONE and card != spec_card): ptr_name = sn.shortname_from_fullname( self.get_attribute_value('name')).name srcctx = self.get_attribute_source_context('target') raise errors.QueryError( f'possibly more than one element returned by an ' f'expression for a computable ' f'{ptr_name!r} ' f"declared as 'single'", context=srcctx) self.set_attribute_value('computable', True) return schema, target, base
def handle_conditional_insert( expr: qlast.InsertQuery, rhs: irast.InsertStmt, lhs_set: Union[irast.Expr, irast.Set], *, ctx: context.ContextLevel, ) -> irast.ConstraintRef: def error(s: str) -> NoReturn: raise errors.QueryError( f'Invalid conditional INSERT statement: {s}', context=expr.context, ) schema = ctx.env.schema if not isinstance(lhs_set, irast.Set): error("left hand side is not SELECT") lhs_set = irutils.unwrap_set(lhs_set) if not isinstance(lhs_set.expr, irast.SelectStmt): error("left hand side is not SELECT") lhs = lhs_set.expr if lhs.result.path_id != rhs.subject.path_id: error("types do not match") if lhs.where: filtered_ptrs = inference.cardinality.extract_filters( lhs.result, lhs.where, scope_tree=ctx.path_scope, singletons=(), env=ctx.env, strict=True) else: filtered_ptrs = None # TODO: Can we support some >1 cases? if not filtered_ptrs or len(filtered_ptrs) > 1: error("does not contain exactly one FILTER clause") return None exclusive_constr: s_constr.Constraint = schema.get('std::exclusive') shape_props = {} for shape_set, _ in rhs.subject.shape: # We need to go through to the base_ptr to get at the # underlying type (instead of the shape's subtype) base_ptr = shape_set.rptr.ptrref.base_ptr if (not isinstance(base_ptr, irast.PointerRef) or not isinstance(shape_set.expr, irast.SelectStmt)): continue schema, pptr = typeutils.ptrcls_from_ptrref(base_ptr, schema=schema) shape_props[pptr] = shape_set.expr.result, base_ptr ptr, ptr_set = filtered_ptrs[0] ptr = ptr.get_nearest_non_derived_parent(schema) if ptr not in shape_props: error("property in FILTER clause does not match INSERT") result, rptr = shape_props[ptr] if not simple_stmt_eq(ptr_set.expr, result.expr, schema): error("value in FILTER clause does not match INSERT") ex_cnstrs = [ c for c in ptr.get_constraints(schema).objects(schema) if c.issubclass(schema, exclusive_constr) and not c.get_subjectexpr(schema) ] if len(ex_cnstrs) != 1 or not ptr.is_property(schema): error("FILTER is not on an exclusive property") ex_cnstr = ex_cnstrs[0] module_id = schema.get_global(s_mod.Module, ptr.get_name(schema).module).id ctx.env.schema = schema return irast.ConstraintRef(id=ex_cnstr.id, module_id=module_id)
def compile_query_subject(expr: irast.Set, *, shape: typing.Optional[typing.List[ qlast.ShapeElement]] = None, view_rptr: typing.Optional[context.ViewRPtr] = None, view_name: typing.Optional[s_name.SchemaName] = None, result_alias: typing.Optional[str] = None, view_scls: typing.Optional[s_types.Type] = None, compile_views: bool = True, is_insert: bool = False, is_update: bool = False, ctx: context.ContextLevel) -> irast.Set: expr_stype = setgen.get_set_type(expr, ctx=ctx) expr_rptr = expr.rptr while isinstance(expr_rptr, irast.TypeIndirectionPointer): expr_rptr = expr_rptr.source.rptr 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.direction is s_pointers.PointerDirection.Outbound and (view_rptr.ptrcls_is_linkprop == (expr_rptr.ptrref.parent_ptr is not None))) if is_ptr_alias: assert view_rptr is not None # 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 # its properties. view_rptr.base_ptrcls = irtyputils.ptrcls_from_ptrref( expr_rptr.ptrref, schema=ctx.env.schema) view_rptr.ptrcls_is_alias = True if (ctx.expr_exposed and viewgen.has_implicit_tid( expr_stype, is_mutation=is_insert or is_update, ctx=ctx) and shape is None and expr_stype not in ctx.env.view_shapes): # Force the subject to be compiled as a view if a __tid__ # insertion is anticipated (the actual decision is taken # by the compile_view_shapes() flow). shape = [] if shape is not None and view_scls is None: if (view_name is None and isinstance(result_alias, s_name.SchemaName)): view_name = result_alias view_scls = viewgen.process_view(stype=expr_stype, path_id=expr.path_id, elements=shape, view_rptr=view_rptr, view_name=view_name, is_insert=is_insert, is_update=is_update, ctx=ctx) if view_scls is not None: expr = setgen.ensure_set(expr, type_override=view_scls, ctx=ctx) expr_stype = view_scls if compile_views: rptr = view_rptr.rptr if view_rptr is not None else None viewgen.compile_view_shapes(expr, rptr=rptr, ctx=ctx) if (shape is not None or view_scls is not None) and len(expr.path_id) == 1: ctx.class_view_overrides[expr.path_id.target.id] = expr_stype return expr
def compile_query_subject( expr: irast.Set, *, shape: typing.Optional[typing.List[qlast.ShapeElement]]=None, view_rptr: typing.Optional[context.ViewRPtr]=None, view_name: typing.Optional[s_name.SchemaName]=None, result_alias: typing.Optional[str]=None, view_scls: typing.Optional[s_types.Type]=None, compile_views: bool=True, is_insert: bool=False, is_update: bool=False, ctx: context.ContextLevel) -> irast.Set: expr_stype = setgen.get_set_type(expr, ctx=ctx) expr_rptr = expr.rptr 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 ( view_rptr.ptrcls_is_linkprop == (expr_rptr.ptrref.parent_ptr is not None) ) ) if is_ptr_alias: # 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 # its properties. rptr = expr_rptr # Unwind type indirections first. while isinstance(rptr, irast.TypeIndirectionPointer): rptr = rptr.source.rptr view_rptr.base_ptrcls = irtyputils.ptrcls_from_ptrref( rptr.ptrref, schema=ctx.env.schema) if shape is not None and view_scls is None: if (view_name is None and isinstance(result_alias, s_name.SchemaName)): view_name = result_alias view_scls = viewgen.process_view( stype=expr_stype, path_id=expr.path_id, elements=shape, view_rptr=view_rptr, view_name=view_name, is_insert=is_insert, is_update=is_update, ctx=ctx) if view_scls is not None: expr = setgen.ensure_set(expr, type_override=view_scls, ctx=ctx) expr_stype = view_scls if compile_views: rptr = view_rptr.rptr if view_rptr is not None else None viewgen.compile_view_shapes(expr, rptr=rptr, ctx=ctx) if (shape is not None or view_scls is not None) and len(expr.path_id) == 1: ctx.class_view_overrides[expr.path_id.target.id] = expr_stype return expr