def compile_inheritance_conflict_selects( stmt: irast.MutatingStmt, conflict: irast.MutatingStmt, typ: s_objtypes.ObjectType, subject_type: s_objtypes.ObjectType, *, ctx: context.ContextLevel, ) -> List[irast.OnConflictClause]: """Compile the selects needed to resolve multiple DML to related types Generate a SELECT that finds all objects of type `typ` that conflict with the insert `stmt`. The backend will use this to explicitly check that no conflicts exist, and raise an error if they do. This is needed because we mostly use triggers to enforce these cross-type exclusive constraints, and they use a snapshot beginning at the start of the statement. """ pointers = _get_exclusive_ptr_constraints(typ, ctx=ctx) exclusive = ctx.env.schema.get('std::exclusive', type=s_constr.Constraint) obj_constrs = [ constr for constr in typ.get_constraints(ctx.env.schema).objects(ctx.env.schema) if constr.issubclass(ctx.env.schema, exclusive) ] shape_ptrs = set() for elem, op in stmt.subject.shape: assert elem.rptr is not None if op != qlast.ShapeOp.MATERIALIZE: shape_ptrs.add(elem.rptr.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 # failed in the error messages) entries: List[Tuple[s_constr.Constraint, ConstraintPair]] = [] for name, (ptr, ptr_constrs) in pointers.items(): for ptr_constr in ptr_constrs: # For updates, we only need to emit the check if we actually # modify a pointer used by the constraint. For inserts, though # everything must be in play, since constraints can depend on # nonexistence also. if ( _constr_matters(ptr_constr, ctx) and ( isinstance(stmt, irast.InsertStmt) or (_get_needed_ptrs(typ, (), [name], ctx)[0] & shape_ptrs) ) ): entries.append((ptr_constr, ({name: (ptr, [ptr_constr])}, []))) for obj_constr in obj_constrs: # See note above about needed ptrs check if ( _constr_matters(obj_constr, ctx) and ( isinstance(stmt, irast.InsertStmt) or (_get_needed_ptrs( typ, [obj_constr], (), ctx)[0] & shape_ptrs) ) ): entries.append((obj_constr, ({}, [obj_constr]))) # For updates, we need to pull from the actual result overlay, # since the final row can depend on things not in the query. fake_dml_set = None if isinstance(stmt, irast.UpdateStmt): fake_subject = qlast.DetachedExpr(expr=qlast.Path(steps=[ s_utils.name_to_ast_ref(subject_type.get_name(ctx.env.schema))])) fake_dml_set = dispatch.compile(fake_subject, ctx=ctx) clauses = [] for cnstr, (p, o) in entries: select_ir, _, _ = compile_conflict_select( stmt, typ, for_inheritance=True, fake_dml_set=fake_dml_set, constrs=p, obj_constrs=o, parser_context=stmt.context, ctx=ctx) if isinstance(select_ir, irast.EmptySet): continue cnstr_ref = irast.ConstraintRef(id=cnstr.id) clauses.append( irast.OnConflictClause( constraint=cnstr_ref, select_ir=select_ir, always_check=False, else_ir=None, else_fail=conflict, update_query_set=fake_dml_set) ) return clauses
def _process_view( *, stype: s_objtypes.ObjectType, path_id: irast.PathId, path_id_namespace: Optional[irast.WeakNamespace] = None, elements: List[qlast.ShapeElement], view_rptr: Optional[context.ViewRPtr] = None, view_name: Optional[sn.QualName] = None, is_insert: bool = False, is_update: bool = False, is_delete: bool = False, parser_context: pctx.ParserContext, ctx: context.ContextLevel, ) -> s_objtypes.ObjectType: if (view_name is None and ctx.env.options.schema_view_mode and view_rptr is not None): # Make sure persistent schema expression aliases have properly formed # names as opposed to the usual mangled form of the ephemeral # aliases. This is needed for introspection readability, as well # as helps in maintaining proper type names for schema # representations that require alphanumeric names, such as # GraphQL. # # We use the name of the source together with the name # of the inbound link to form the name, so in e.g. # CREATE ALIAS V := (SELECT Foo { bar: { baz: { ... } }) # The name of the innermost alias would be "__V__bar__baz". source_name = view_rptr.source.get_name(ctx.env.schema).name if not source_name.startswith('__'): source_name = f'__{source_name}' if view_rptr.ptrcls_name is not None: ptr_name = view_rptr.ptrcls_name.name elif view_rptr.ptrcls is not None: ptr_name = view_rptr.ptrcls.get_shortname(ctx.env.schema).name else: raise errors.InternalServerError( '_process_view in schema mode received view_rptr with ' 'neither ptrcls_name, not ptrcls' ) name = f'{source_name}__{ptr_name}' view_name = sn.QualName( module=ctx.derived_target_module or '__derived__', name=name, ) view_scls = schemactx.derive_view( stype, is_insert=is_insert, is_update=is_update, is_delete=is_delete, derived_name=view_name, ctx=ctx, ) assert isinstance(view_scls, s_objtypes.ObjectType), view_scls is_mutation = is_insert or is_update is_defining_shape = ctx.expr_exposed or is_mutation if view_rptr is not None and view_rptr.ptrcls is None: derive_ptrcls( view_rptr, target_scls=view_scls, transparent=True, ctx=ctx) pointers = [] for shape_el in elements: with ctx.newscope(fenced=True) as scopectx: pointer = _normalize_view_ptr_expr( shape_el, view_scls, path_id=path_id, path_id_namespace=path_id_namespace, is_insert=is_insert, is_update=is_update, view_rptr=view_rptr, ctx=scopectx) if pointer in pointers: schema = ctx.env.schema vnp = pointer.get_verbosename(schema, with_parent=True) raise errors.QueryError( f'duplicate definition of {vnp}', context=shape_el.context) pointers.append(pointer) if is_insert: explicit_ptrs = { ptrcls.get_local_name(ctx.env.schema) for ptrcls in pointers } scls_pointers = stype.get_pointers(ctx.env.schema) for pn, ptrcls in scls_pointers.items(ctx.env.schema): if (pn in explicit_ptrs or ptrcls.is_pure_computable(ctx.env.schema)): continue default_expr = ptrcls.get_default(ctx.env.schema) if not default_expr: if ( ptrcls.get_required(ctx.env.schema) and pn != sn.UnqualName('__type__') ): if ptrcls.is_property(ctx.env.schema): # If the target is a sequence, there's no need # for an explicit value. ptrcls_target = ptrcls.get_target(ctx.env.schema) assert ptrcls_target is not None if ptrcls_target.issubclass( ctx.env.schema, ctx.env.schema.get( 'std::sequence', type=s_objects.SubclassableObject)): continue vn = ptrcls.get_verbosename( ctx.env.schema, with_parent=True) raise errors.MissingRequiredError( f'missing value for required {vn}') else: continue ptrcls_sn = ptrcls.get_shortname(ctx.env.schema) default_ql = qlast.ShapeElement( expr=qlast.Path( steps=[ qlast.Ptr( ptr=qlast.ObjectRef( name=ptrcls_sn.name, module=ptrcls_sn.module, ), ), ], ), compexpr=qlast.DetachedExpr( expr=default_expr.qlast, ), ) with ctx.newscope(fenced=True) as scopectx: pointers.append( _normalize_view_ptr_expr( default_ql, view_scls, path_id=path_id, path_id_namespace=path_id_namespace, is_insert=is_insert, is_update=is_update, from_default=True, view_rptr=view_rptr, ctx=scopectx, ), ) elif ( stype.get_name(ctx.env.schema).module == 'schema' and ctx.env.options.apply_query_rewrites ): explicit_ptrs = { ptrcls.get_local_name(ctx.env.schema) for ptrcls in pointers } scls_pointers = stype.get_pointers(ctx.env.schema) for pn, ptrcls in scls_pointers.items(ctx.env.schema): if ( pn in explicit_ptrs or ptrcls.is_pure_computable(ctx.env.schema) ): continue schema_deflt = ptrcls.get_schema_reflection_default(ctx.env.schema) if schema_deflt is None: continue with ctx.newscope(fenced=True) as scopectx: ptr_ref = s_utils.name_to_ast_ref(pn) implicit_ql = qlast.ShapeElement( expr=qlast.Path(steps=[qlast.Ptr(ptr=ptr_ref)]), compexpr=qlast.BinOp( left=qlast.Path( partial=True, steps=[ qlast.Ptr( ptr=ptr_ref, direction=( s_pointers.PointerDirection.Outbound ), ) ], ), right=qlparser.parse_fragment(schema_deflt), op='??', ), ) # Note: we only need to record the schema default # as a computable, but not include it in the type # shape, so we ignore the return value. _normalize_view_ptr_expr( implicit_ql, view_scls, path_id=path_id, path_id_namespace=path_id_namespace, is_insert=is_insert, is_update=is_update, view_rptr=view_rptr, ctx=scopectx, ) for ptrcls in pointers: source: Union[s_types.Type, s_pointers.PointerLike] if ptrcls.is_link_property(ctx.env.schema): assert view_rptr is not None and view_rptr.ptrcls is not None source = view_rptr.ptrcls else: source = view_scls if is_defining_shape: cinfo = ctx.source_map.get(ptrcls) if cinfo is not None: shape_op = cinfo.shape_op else: shape_op = qlast.ShapeOp.ASSIGN ctx.env.view_shapes[source].append((ptrcls, shape_op)) if (view_rptr is not None and view_rptr.ptrcls is not None and view_scls != stype): ctx.env.schema = view_scls.set_field_value( ctx.env.schema, 'rptr', view_rptr.ptrcls) return view_scls
def object_type_to_python_type( objtype: s_objtypes.ObjectType, schema: s_schema.Schema, *, base_class: Optional[type] = None, _memo: Optional[Dict[s_types.Type, type]] = None, ) -> type: if _memo is None: _memo = {} default: Any fields = [] subclasses = [] for pn, p in objtype.get_pointers(schema).items(schema): str_pn = str(pn) if str_pn in ('id', '__type__'): continue ptype = p.get_target(schema) assert ptype is not None if isinstance(ptype, s_objtypes.ObjectType): pytype = _memo.get(ptype) if pytype is None: pytype = object_type_to_python_type( ptype, schema, base_class=base_class, _memo=_memo) _memo[ptype] = pytype for subtype in ptype.children(schema): subclasses.append( object_type_to_python_type( subtype, schema, base_class=pytype, _memo=_memo)) else: pytype = scalar_type_to_python_type(ptype, schema) ptr_card = p.get_cardinality(schema) is_multi = ptr_card.is_multi() if is_multi: pytype = FrozenSet[pytype] # type: ignore default = p.get_default(schema) if default is None: if p.get_required(schema): default = dataclasses.MISSING else: default = qlcompiler.evaluate_to_python_val( default.text, schema=schema) if is_multi and not isinstance(default, frozenset): default = frozenset((default,)) constraints = p.get_constraints(schema).objects(schema) exclusive = schema.get('std::exclusive', type=s_constr.Constraint) unique = ( not ptype.is_object_type() and any(c.issubclass(schema, exclusive) for c in constraints) ) field = dataclasses.field( compare=unique, hash=unique, repr=True, default=default, ) fields.append((str_pn, pytype, field)) bases: Tuple[type, ...] if base_class is not None: bases = (base_class,) else: bases = () ptype_dataclass = dataclasses.make_dataclass( objtype.get_name(schema).name, fields=fields, bases=bases, frozen=True, namespace={'_subclasses': subclasses}, ) assert isinstance(ptype_dataclass, type) return ptype_dataclass