def compile_Set(expr: qlast.Set, *, ctx: context.ContextLevel) -> irast.Set: # after flattening the set may still end up with 0 or 1 element, # which are treated as a special case elements = flatten_set(expr) if elements: if len(elements) == 1: # From the scope perspective, single-element set # literals are equivalent to a binary UNION with # an empty set, not to the element. with ctx.newscope(fenced=True) as scopectx: ir_set = dispatch.compile(elements[0], ctx=scopectx) return setgen.scoped_set(ir_set, ctx=scopectx) else: # a set literal is just sugar for a UNION op = 'UNION' # Turn it into a tree of UNIONs so we only blow up the nesting # depth logarithmically. # TODO: Introduce an N-ary operation that handles the whole thing? mid = len(elements) // 2 ls, rs = elements[:mid], elements[mid:] bigunion = qlast.BinOp( left=qlast.Set(elements=ls) if len(ls) > 1 else ls[0], right=qlast.Set(elements=rs) if len(rs) > 1 else rs[0], op=op) return dispatch.compile(bigunion, ctx=ctx) else: return setgen.new_empty_set( alias=ctx.aliases.get('e'), ctx=ctx, srcctx=expr.context, )
def nullify_expr_field(self, schema, context, field, op): from edb.edgeql.compiler import astutils as qlastutils if field.name == 'expr': base = self.get_attribute_value('bases').first(schema) if base.is_object_type(): base_name = base.get_name(schema) ql = qlast.SelectQuery( result=qlast.Path(steps=[ qlast.ObjectRef( name=base_name.name, module=base_name.module, ), ], ), where=qlast.BooleanConstant(value='false'), ) else: ql = qlast.TypeCast( expr=qlast.Set(), type=qlastutils.type_to_ql_typeref(base, schema=schema), ) op.new_value = s_expr.Expression.compiled( s_expr.Expression.from_ast(ql, schema=schema, modaliases=context.modaliases), schema=schema, ) return True else: super().nullify_expr_field(schema, context, field, op)
def nullify_expr_field(self, schema, context, field, op): from edb.edgeql.compiler import astutils as qlastutils if field.name == 'default': metaclass = self.get_schema_metaclass() target_ref = self.get_attribute_value('target') target = self._resolve_attr_value(target_ref, 'target', metaclass.get_field('target'), schema) target_ql = qlastutils.type_to_ql_typeref(target, schema=schema) empty = qlast.TypeCast( expr=qlast.Set(), type=target_ql, ) op.new_value = s_expr.Expression.from_ast( empty, schema=schema, modaliases=context.modaliases, ) return True else: super().nullify_expr_field(schema, context, field, op)
def _apply_field_ast( self, schema: s_schema.Schema, context: sd.CommandContext, node: qlast.DDLOperation, op: sd.AlterObjectProperty, ) -> None: objtype = self.get_referrer_context(context) if op.property == 'target' and objtype: # Due to how SDL is processed the underlying AST may be an # AlterConcreteLink, which requires different handling. if isinstance(node, qlast.CreateConcreteLink): if not node.target: expr = self.get_attribute_value('expr') if expr is not None: node.target = expr.qlast else: t = op.new_value assert isinstance(t, (so.Object, so.ObjectShell)) node.target = utils.typeref_to_ast(schema, t) else: old_type = pointers.merge_target( self.scls, list(self.scls.get_bases(schema).objects(schema)), 'target', ignore_local=True, schema=schema, ) assert isinstance(op.new_value, (so.Object, so.ObjectShell)) new_type = ( op.new_value.resolve(schema) if isinstance(op.new_value, so.ObjectShell) else op.new_value) new_type_ast = utils.typeref_to_ast(schema, op.new_value) cast_expr = None # If the type isn't assignment castable, generate a # USING with a nonsense cast. It shouldn't matter, # since there should be no data to cast, but the DDL side # of things doesn't know that since the command is split up. if old_type and not old_type.assignment_castable_to( new_type, schema): cast_expr = qlast.TypeCast( type=new_type_ast, expr=qlast.Set(elements=[]), ) node.commands.append( qlast.SetPointerType( value=new_type_ast, cast_expr=cast_expr, ) ) elif op.property == 'on_target_delete': node.commands.append(qlast.OnTargetDelete(cascade=op.new_value)) else: super()._apply_field_ast(schema, context, node, op)
def compile_conflict_select( stmt: irast.MutatingStmt, subject_typ: s_objtypes.ObjectType, *, for_inheritance: bool=False, fake_dml_set: Optional[irast.Set]=None, obj_constrs: Sequence[s_constr.Constraint], constrs: PointerConstraintMap, parser_context: Optional[pctx.ParserContext], ctx: context.ContextLevel, ) -> Tuple[irast.Set, bool, bool]: """Synthesize a select of conflicting objects This teases apart the constraints we care about based on which type they originate from, generates a SELECT for each type, and unions them together. `cnstrs` contains the constraints to consider. """ schema = ctx.env.schema if for_inheritance: type_maps = {subject_typ: (constrs, list(obj_constrs))} else: type_maps = _split_constraints(obj_constrs, constrs, ctx=ctx) # Generate a separate query for each type from_parent = False frags = [] for a_obj, (a_constrs, a_obj_constrs) in type_maps.items(): frag = _compile_conflict_select( stmt, a_obj, obj_constrs=a_obj_constrs, constrs=a_constrs, for_inheritance=for_inheritance, fake_dml_set=fake_dml_set, parser_context=parser_context, ctx=ctx, ) if frag: if a_obj != subject_typ: from_parent = True frags.append(frag) always_check = from_parent or any( not child.is_view(schema) for child in subject_typ.children(schema) ) # Union them all together select_ast = qlast.Set(elements=frags) with ctx.new() as ectx: ectx.implicit_limit = 0 select_ir = dispatch.compile(select_ast, ctx=ectx) select_ir = setgen.scoped_set( select_ir, force_reassign=True, ctx=ectx) assert isinstance(select_ir, irast.Set) return select_ir, always_check, from_parent
def reduce_LBRACE_OptExprList_RBRACE(self, *kids): self.val = qlast.Set(elements=kids[1].val)
def _compile_conflict_select( stmt: irast.MutatingStmt, subject_typ: s_objtypes.ObjectType, *, for_inheritance: bool, fake_dml_set: Optional[irast.Set], obj_constrs: Sequence[s_constr.Constraint], constrs: Dict[str, Tuple[s_pointers.Pointer, List[s_constr.Constraint]]], parser_context: Optional[pctx.ParserContext], ctx: context.ContextLevel, ) -> Optional[qlast.Expr]: """Synthesize a select of conflicting objects ... for a single object type. This gets called once for each ancestor type that provides constraints to the type being inserted. `cnstrs` contains the constraints to consider. """ # Find which pointers we need to grab needed_ptrs, ptr_anchors = _get_needed_ptrs( subject_typ, obj_constrs, constrs.keys(), ctx=ctx ) ctx.anchors = ctx.anchors.copy() # If we are given a fake_dml_set to directly represent the result # of our DML, use that instead of populating the result. if fake_dml_set: for p in needed_ptrs | {'id'}: ptr = subject_typ.getptr(ctx.env.schema, s_name.UnqualName(p)) val = setgen.extend_path(fake_dml_set, ptr, ctx=ctx) ptr_anchors[p] = ctx.create_anchor(val, p) # Find the IR corresponding to the fields we care about and # produce anchors for them ptrs_in_shape = set() for elem, _ in stmt.subject.shape: assert elem.rptr is not None name = elem.rptr.ptrref.shortname.name ptrs_in_shape.add(name) if name in needed_ptrs and name not in ptr_anchors: assert elem.expr if inference.infer_volatility(elem.expr, ctx.env).is_volatile(): if for_inheritance: error = ( 'INSERT does not support volatile properties with ' 'exclusive constraints when another statement in ' 'the same query modifies a related type' ) else: error = ( 'INSERT UNLESS CONFLICT ON does not support volatile ' 'properties' ) raise errors.UnsupportedFeatureError( error, context=parser_context ) # We want to use the same path_scope_id as the original elem_set = setgen.ensure_set(elem.expr, ctx=ctx) elem_set.path_scope_id = elem.path_scope_id # FIXME: The wrong thing will definitely happen if there are # volatile entries here ptr_anchors[name] = ctx.create_anchor(elem_set, name) if for_inheritance and not ptrs_in_shape: return None # Fill in empty sets for pointers that are needed but not present present_ptrs = set(ptr_anchors) for p in (needed_ptrs - present_ptrs): ptr = subject_typ.getptr(ctx.env.schema, s_name.UnqualName(p)) typ = ptr.get_target(ctx.env.schema) assert typ ptr_anchors[p] = qlast.TypeCast( expr=qlast.Set(elements=[]), type=typegen.type_to_ql_typeref(typ, ctx=ctx)) if not ptr_anchors: raise errors.QueryError( 'INSERT UNLESS CONFLICT property requires matching shape', context=parser_context, ) conds: List[qlast.Expr] = [] for ptrname, (ptr, ptr_cnstrs) in constrs.items(): if ptrname not in present_ptrs: continue anchor = qlutils.subject_paths_substitute( ptr_anchors[ptrname], ptr_anchors) ptr_val = qlast.Path(partial=True, steps=[ qlast.Ptr(ptr=qlast.ObjectRef(name=ptrname)) ]) ptr, ptr_cnstrs = constrs[ptrname] ptr_card = ptr.get_cardinality(ctx.env.schema) for cnstr in ptr_cnstrs: lhs: qlast.Expr = anchor rhs: qlast.Expr = ptr_val # If there is a subjectexpr, substitute our lhs and rhs in # for __subject__ in the subjectexpr and compare *that* if (subjectexpr := cnstr.get_subjectexpr(ctx.env.schema)): assert isinstance(subjectexpr.qlast, qlast.Expr) lhs = qlutils.subject_substitute(subjectexpr.qlast, lhs) rhs = qlutils.subject_substitute(subjectexpr.qlast, rhs) conds.append(qlast.BinOp( op='=' if ptr_card.is_single() else 'IN', left=lhs, right=rhs, ))
assert subjectexpr and isinstance(subjectexpr.qlast, qlast.Expr) lhs = qlutils.subject_paths_substitute(subjectexpr.qlast, ptr_anchors) rhs = qlutils.subject_substitute(subjectexpr.qlast, insert_subject) conds.append(qlast.BinOp(op='=', left=lhs, right=rhs)) if not conds: return None # We use `any` to compute the disjunction here because some might # be empty. if len(conds) == 1: cond = conds[0] else: cond = qlast.FunctionCall( func='any', args=[qlast.Set(elements=conds)], ) # For the result filtering we need to *ignore* the same object if fake_dml_set: anchor = qlutils.subject_paths_substitute( ptr_anchors['id'], ptr_anchors) ptr_val = qlast.Path(partial=True, steps=[ qlast.Ptr(ptr=qlast.ObjectRef(name='id')) ]) cond = qlast.BinOp( op='AND', left=cond, right=qlast.BinOp(op='!=', left=anchor, right=ptr_val), )
def compile_result_clause( result: qlast.Expr, *, view_scls: Optional[s_types.Type]=None, view_rptr: Optional[context.ViewRPtr]=None, view_name: Optional[s_name.QualName]=None, result_alias: Optional[str]=None, forward_rptr: bool=False, ctx: context.ContextLevel) -> irast.Set: with ctx.new() as sctx: if sctx.stmt is ctx.toplevel_stmt: sctx.expr_exposed = True if forward_rptr: sctx.view_rptr = view_rptr # sctx.view_scls = view_scls result_expr: qlast.Expr shape: Optional[Sequence[qlast.ShapeElement]] if isinstance(result, qlast.Shape): result_expr = result.expr shape = result.elements else: result_expr = result shape = None if result_alias: # `SELECT foo := expr` is equivalent to # `WITH foo := expr SELECT foo` rexpr = astutils.ensure_ql_select(result_expr) if ( sctx.implicit_limit and rexpr.limit is None and not sctx.inhibit_implicit_limit ): # Inline alias is special: it's both "exposed", # but also subject for further processing, so # make sure we don't mangle it with an implicit # limit. rexpr.limit = qlast.TypeCast( expr=qlast.Set(), type=qlast.TypeName( maintype=qlast.ObjectRef( module='__std__', name='int64', ) ) ) stmtctx.declare_view( rexpr, alias=s_name.UnqualName(result_alias), ctx=sctx, ) result_expr = qlast.Path( steps=[qlast.ObjectRef(name=result_alias)] ) if (view_rptr is not None and (view_rptr.is_insert or view_rptr.is_update) and view_rptr.ptrcls is not None) and False: # If we have an empty set assigned to a pointer in an INSERT # or UPDATE, there's no need to explicitly specify the # empty set type and it can be assumed to match the pointer # target type. target_t = view_rptr.ptrcls.get_target(ctx.env.schema) if astutils.is_ql_empty_set(result_expr): expr = setgen.new_empty_set( stype=target_t, alias=ctx.aliases.get('e'), ctx=sctx, srcctx=result_expr.context, ) else: with sctx.new() as exprctx: exprctx.empty_result_type_hint = target_t expr = setgen.ensure_set( dispatch.compile(result_expr, ctx=exprctx), ctx=exprctx) else: if astutils.is_ql_empty_set(result_expr): expr = setgen.new_empty_set( stype=sctx.empty_result_type_hint, alias=ctx.aliases.get('e'), ctx=sctx, srcctx=result_expr.context, ) else: expr = setgen.ensure_set( dispatch.compile(result_expr, ctx=sctx), ctx=sctx) ctx.partial_path_prefix = expr ir_result = compile_query_subject( expr, shape=shape, view_rptr=view_rptr, view_name=view_name, result_alias=result_alias, view_scls=view_scls, compile_views=ctx.stmt is ctx.toplevel_stmt, ctx=sctx, parser_context=result.context) ctx.partial_path_prefix = ir_result return ir_result
assert elem.expr # FIXME: The wrong thing will definitely happen if there are # volatile entries here source_alias = ctx.aliases.get(name) ctx.anchors[source_alias] = setgen.ensure_set(elem.expr, ctx=ctx) ptr_anchors[name] = ( qlast.Path(steps=[qlast.ObjectRef(name=source_alias)])) # Fill in empty sets for pointers that are needed but not present present_ptrs = set(ptr_anchors) for p in (needed_ptrs - present_ptrs): ptr = subject_typ.getptr(ctx.env.schema, s_name.UnqualName(p)) typ = ptr.get_target(ctx.env.schema) assert typ ptr_anchors[p] = qlast.TypeCast( expr=qlast.Set(elements=[]), type=typegen.type_to_ql_typeref(typ, ctx=ctx)) if not ptr_anchors: raise errors.QueryError( 'INSERT UNLESS CONFLICT property requires matching shape', context=parser_context, ) conds: List[qlast.Expr] = [] for ptrname, (ptr, ptr_cnstrs) in constrs.items(): if ptrname not in present_ptrs: continue anchor = qlutils.subject_paths_substitute( ptr_anchors[ptrname], ptr_anchors) ptr_val = qlast.Path(partial=True, steps=[