def _float_to_path(self, token, context): from edb.schema import pointers as s_pointers # make sure that the float is of the type 0.1 parts = token.val.split('.') if not (len(parts) == 2 and parts[0].isdigit() and parts[1].isdigit()): raise EdgeQLSyntaxError(f"Unexpected {token.val!r}", context=token.context) # context for the AST is established manually here return [ qlast.Ptr( ptr=qlast.ObjectRef( name=parts[0], context=token.context, ), direction=s_pointers.PointerDirection.Outbound, context=context, ), qlast.Ptr( ptr=qlast.ObjectRef( name=parts[1], context=token.context, ), direction=s_pointers.PointerDirection.Outbound, context=token.context, ) ]
def _inject_tname( insert_stmt: qlast.InsertQuery, *, ctx: context.ContextLevel) -> None: for el in insert_stmt.shape: if isinstance(el.compexpr, qlast.InsertQuery): _inject_tname(el.compexpr, ctx=ctx) assert isinstance(insert_stmt.subject.steps[0], qlast.BaseObjectRef) insert_stmt.shape.append( qlast.ShapeElement( expr=qlast.Path( steps=[qlast.Ptr(ptr=qlast.ObjectRef(name='_tname'))], ), compexpr=qlast.Path( steps=[ qlast.Introspect( type=qlast.TypeName( maintype=insert_stmt.subject.steps[0], ), ), qlast.Ptr(ptr=qlast.ObjectRef(name='name')), ], ), ), )
def visit_order(self, node): if not isinstance(node, gql_ast.ObjectValue): raise g_errors.GraphQLTranslationError( f'an object is expected for "order"') # if there is no specific ordering, then order by id if not node.fields: return [ qlast.SortExpr( path=qlast.Path( steps=[qlast.Ptr(ptr=qlast.ObjectRef(name='id'))], partial=True, ), direction=qlast.SortAsc, ) ] # Ordering is handled by specifying a list of special Ordering objects. # Validation is already handled by this point. orderby = [] for enum in node.fields: name, direction, nulls = self._visit_order_item(enum) orderby.append( qlast.SortExpr( path=qlast.Path( steps=[qlast.Ptr(ptr=qlast.ObjectRef(name=name))], partial=True, ), direction=direction, nones_order=nulls, )) return orderby
def get_config_type_shape( schema: s_schema.Schema, stype: s_objtypes.ObjectType, path: List[qlast.Base], ) -> List[qlast.ShapeElement]: from . import objtypes as s_objtypes shape = [] seen: Set[str] = set() stypes = [stype] + list(stype.descendants(schema)) for t in stypes: t_name = t.get_name(schema) for unqual_pn, p in t.get_pointers(schema).items(schema): pn = str(unqual_pn) if pn in ('id', '__type__') or pn in seen: continue elem_path: List[qlast.Base] = [] if t != stype: elem_path.append( qlast.TypeIntersection(type=qlast.TypeName( maintype=qlast.ObjectRef( module=t_name.module, name=t_name.name, ), ), ), ) elem_path.append(qlast.Ptr(ptr=qlast.ObjectRef(name=pn))) ptype = p.get_target(schema) assert ptype is not None if isinstance(ptype, s_objtypes.ObjectType): subshape = get_config_type_shape(schema, ptype, path + elem_path) subshape.append( qlast.ShapeElement( expr=qlast.Path(steps=[ qlast.Ptr(ptr=qlast.ObjectRef(name='_tname'), ), ], ), compexpr=qlast.Path(steps=path + elem_path + [ qlast.Ptr(ptr=qlast.ObjectRef(name='__type__')), qlast.Ptr(ptr=qlast.ObjectRef(name='name')), ], ), ), ) else: subshape = [] shape.append( qlast.ShapeElement( expr=qlast.Path(steps=elem_path), elements=subshape, ), ) seen.add(pn) return shape
def _inline_type_computable( ir_set: irast.Set, stype: s_objtypes.ObjectType, compname: str, propname: str, *, shape_ptrs: List[ShapePtr], ctx: context.ContextLevel, ) -> None: assert isinstance(stype, s_objtypes.ObjectType) ptr: Optional[s_pointers.Pointer] try: ptr = setgen.resolve_ptr(stype, compname, track_ref=None, ctx=ctx) # The pointer might exist on the base type. That doesn't count, # and we need to re-inject it. if not ptr.get_computable(ctx.env.schema): ptr = None except errors.InvalidReferenceError: ptr = None if ptr is None: ql = qlast.ShapeElement( expr=qlast.Path( steps=[qlast.Ptr( ptr=qlast.ObjectRef(name=compname), 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=propname), direction=s_pointers.PointerDirection.Outbound, ) ] ) ) with ctx.newscope(fenced=True) as scopectx: scopectx.anchors = scopectx.anchors.copy() scopectx.anchors[qlast.Source().name] = ir_set ptr = _normalize_view_ptr_expr( ql, stype, path_id=ir_set.path_id, ctx=scopectx) view_shape = ctx.env.view_shapes[stype] view_shape_ptrs = {p for p, _ in view_shape} if ptr not in view_shape_ptrs: view_shape.insert(0, (ptr, qlast.ShapeOp.ASSIGN)) shape_ptrs.insert(0, (ir_set, ptr, qlast.ShapeOp.ASSIGN))
def get_config_type_shape(schema, stype, path) -> typing.List[qlast.ShapeElement]: shape = [] seen = set() stypes = [stype] + list(stype.descendants(schema)) for t in stypes: t_name = t.get_name(schema) for pn, p in t.get_pointers(schema).items(schema): if pn in ('id', '__type__') or pn in seen: continue elem_path = [] if t is not stype: elem_path.append( qlast.TypeIndirection(type=qlast.TypeName( maintype=qlast.ObjectRef( module=t_name.module, name=t_name.name, ), ), ), ) elem_path.append(qlast.Ptr(ptr=qlast.ObjectRef(name=pn))) ptype = p.get_target(schema) if ptype.is_object_type(): subshape = get_config_type_shape(schema, ptype, path + elem_path) subshape.append( qlast.ShapeElement( expr=qlast.Path(steps=[ qlast.Ptr(ptr=qlast.ObjectRef(name='_tname'), ), ], ), compexpr=qlast.Path(steps=path + elem_path + [ qlast.Ptr(ptr=qlast.ObjectRef(name='__type__')), qlast.Ptr(ptr=qlast.ObjectRef(name='name')), ], ), ), ) else: subshape = [] shape.append( qlast.ShapeElement( expr=qlast.Path(steps=elem_path), elements=subshape, ), ) seen.add(pn) return shape
def _prepare_field(self, node): path = self._context.path[-1] include_base = self._context.include_base[-1] is_top = self._is_top_level_field(node) spath = self._context.path[-1] prevt, target = self._get_parent_and_current_type() # insert normal or specialized link steps = [] if include_base: base = spath[0].type steps.append(qlast.TypeIndirection( type=qlast.TypeName( maintype=qlast.ObjectRef( module=base.module, name=base.short_name ), ), )) steps.append(qlast.Ptr( ptr=qlast.ObjectRef( name=node.name.value ) )) return is_top, path, prevt, target, steps
def get_path_prefix(self, end_trim=None): # flatten the path path = [step for psteps in self._context.path for step in psteps] # find the first shadowed root prev_base = None for i, step in enumerate(path): base = step.type # if the field is specifically shadowed, then this is # appropriate shadow base if (prev_base is not None and prev_base.is_field_shadowed(step.name)): base = prev_base break # otherwise the base must be shadowing an entire type elif isinstance(base, gt.GQLShadowType): break prev_base = base # trim the rest of the path path = path[i + 1:end_trim] prefix = [qlast.ObjectRef(module=base.module, name=base.short_name)] prefix.extend( qlast.Ptr(ptr=qlast.ObjectRef(name=step.name)) for step in path) return prefix
def reduce_PathStepName(self, *kids): from edb.schema import pointers as s_pointers self.val = qlast.Path(steps=[ qlast.Ptr(ptr=kids[0].val, direction=s_pointers.PointerDirection.Outbound) ])
def reduce_DOTBW_PathStepName(self, *kids): from edb.schema import pointers as s_pointers self.val = qlast.Ptr( ptr=kids[1].val, direction=s_pointers.PointerDirection.Inbound )
def reduce_ShapePathPtr_DOT_PathStepName(self, *kids): from edb.schema import pointers as s_pointers self.val = qlast.Path(steps=[ qlast.TypeIndirection(type=kids[0].val, ), qlast.Ptr(ptr=kids[2].val, direction=s_pointers.PointerDirection.Outbound), ])
def reduce_AT_ShortNodeName(self, *kids): from edb.schema import pointers as s_pointers self.val = qlast.Ptr( ptr=kids[1].val, direction=s_pointers.PointerDirection.Outbound, type='property' )
def reduce_DOT_ICONST(self, *kids): # this is a valid link-like syntax for accessing unnamed tuples from edb.schema import pointers as s_pointers self.val = qlast.Ptr( ptr=qlast.ObjectRef(name=kids[1].val), direction=s_pointers.PointerDirection.Outbound )
def reduce_AT_ShortNodeName(self, *kids): self.val = qlast.Path( steps=[ qlast.Ptr( ptr=kids[1].val, type='property' ) ] )
def extend_path(expr: qlast.Expr, field: str) -> qlast.Path: step = qlast.Ptr(ptr=qlast.ObjectRef(name=field)) if isinstance(expr, qlast.Path): return qlast.Path( steps=[*expr.steps, step], partial=expr.partial, ) else: return qlast.Path(steps=[expr, step])
def visit_ObjectField(self, node): fname = node.name.value # handle boolean ops if fname == 'and': return self._visit_list_of_inputs(node.value, 'AND') elif fname == 'or': return self._visit_list_of_inputs(node.value, 'OR') elif fname == 'not': return qlast.UnaryOp(op='NOT', operand=self.visit(node.value)) # handle various scalar ops op = gt.GQL_TO_OPS_MAP.get(fname) if op: value = self.visit(node.value) return qlast.BinOp(left=self._context.filter, op=op, right=value) # we're at the beginning of a scalar op _, target = self._get_parent_and_current_type() name = self.get_path_prefix() name.append(qlast.Ptr(ptr=qlast.ObjectRef(name=fname))) name = qlast.Path(steps=name) ftype = target.get_field_type(fname) typename = ftype.name if typename not in {'std::str', 'std::uuid'}: gql_type = gt.EDB_TO_GQL_SCALARS_MAP.get(typename) if gql_type == graphql.GraphQLString: # potentially need to cast the 'name' side into a # <str>, so as to be compatible with the 'value' name = qlast.TypeCast( expr=name, type=qlast.TypeName(maintype=qlast.ObjectRef(name='str')), ) self._context.filter = name value = self.visit(node.value) # we need to cast a target string into <uuid> or enum if typename == 'std::uuid' and not isinstance(value.right, qlast.TypeCast): value.right = qlast.TypeCast( expr=value.right, type=qlast.TypeName(maintype=qlast.ObjectRef(name='uuid')), ) elif ftype.is_enum(): value.right = qlast.TypeCast( expr=value.right, type=qlast.TypeName(maintype=qlast.ObjectRef(name=ftype.name)), ) return value
def make_free_object(els: Dict[str, qlast.Expr]) -> qlast.Shape: return qlast.Shape( expr=None, elements=[ qlast.ShapeElement( expr=qlast.Path( steps=[qlast.Ptr(ptr=qlast.ObjectRef(name=name))]), compexpr=expr ) for name, expr in els.items() ], )
def reduce_PathStepName_OptTypeIntersection(self, *kids): from edb.schema import pointers as s_pointers steps = [ qlast.Ptr(ptr=kids[0].val, direction=s_pointers.PointerDirection.Outbound), ] if kids[1].val is not None: steps.append(kids[1].val) self.val = qlast.Path(steps=steps)
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: assert isinstance(op.new_value, (so.Object, so.ObjectShell)) top_op = self.get_top_referrer_op(context) conv_expr: Optional[qlast.Expr] if (top_op is not None and (isinstance(top_op, sd.CreateObject) or (top_op.orig_cmd_type is not None and issubclass(top_op.orig_cmd_type, sd.CreateObject)))): # This op is part of CREATE TYPE, so avoid tripping up # the DDL check on SET TYPE by generating an appropriate # conversion expression: USING (.foo[IS Type]). lname = sn.shortname_from_fullname(self.classname).name conv_expr = qlast.Path( partial=True, steps=[ qlast.Ptr(ptr=qlast.ObjectRef(name=lname)), qlast.TypeIntersection(type=utils.typeref_to_ast( schema, op.new_value)) ], ) else: conv_expr = None node.commands.append( qlast.SetPointerType( value=utils.typeref_to_ast(schema, op.new_value), expr=conv_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 _visit_path(self, node): steps = [] while node.rptr: if node.show_as_anchor and not self.context.inline_anchors: break if isinstance(node.rptr.ptrref, irast.TypeIndirectionPointerRef): ttype = irtyputils.ir_typeref_to_type(self.context.schema, node.typeref) steps.append( qlast.TypeIndirection(type=typegen.type_to_ql_typeref( ttype, ctx=self.context))) else: rptr = node.rptr ptrref = rptr.ptrref pname = ptrref.shortname link = qlast.Ptr( ptr=qlast.ObjectRef(name=pname.name, ), direction=rptr.direction, ) if ptrref.parent_ptr is not None: link.type = 'property' steps.append(link) node = node.rptr.source if node.show_as_anchor and not self.context.inline_anchors: if issubclass(node.show_as_anchor, qlast.Expr): step = node.show_as_anchor() else: step = qlast.ObjectRef(name=node.show_as_anchor) else: if node.typeref.material_type is not None: typeref = node.typeref.material_type else: typeref = node.typeref stype = self.context.schema.get_by_id(typeref.id) scls_shortname = stype.get_shortname(self.context.schema) step = qlast.ObjectRef(name=scls_shortname.name, module=scls_shortname.module) return qlast.Path(steps=[step] + list(reversed(steps)))
def visit_Set(self, node): if node.expr is not None: result = self.visit(node.expr) else: result = self._visit_path(node) if node.shape: result = qlast.Shape(expr=result, elements=[]) for el in node.shape: rptr = el.rptr ptrref = rptr.ptrref pn = ptrref.shortname pn = qlast.ShapeElement(expr=qlast.Path(steps=[ qlast.Ptr(ptr=qlast.ObjectRef(name=pn.name), direction=rptr.direction) ])) result.elements.append(pn) return result
def computable_ptr_set( rptr: irast.Pointer, *, unnest_fence: bool = False, same_computable_scope: bool = False, srcctx: Optional[parsing.ParserContext] = None, ctx: context.ContextLevel, ) -> irast.Set: """Return ir.Set for a pointer defined as a computable.""" ptrcls = typegen.ptrcls_from_ptrref(rptr.ptrref, ctx=ctx) 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 the parent of derived ptrcls. 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: source_rptrref = source_set.rptr.ptrref if source_rptrref.base_ptr is not None: source_rptrref = source_rptrref.base_ptr source_set.rptr = irast.Pointer( source=source_set.rptr.source, target=source_set, ptrref=source_rptrref, direction=source_set.rptr.direction, ) qlctx: Optional[context.ContextLevel] inner_source_path_id: Optional[irast.PathId] try: comp_info = ctx.source_map[ptrcls] qlexpr = comp_info.qlexpr assert isinstance(comp_info.context, context.ContextLevel) qlctx = comp_info.context inner_source_path_id = comp_info.path_id path_id_ns = comp_info.path_id_ns except KeyError: comp_expr = ptrcls.get_expr(ctx.env.schema) schema_qlexpr: Optional[qlast.Expr] = None if comp_expr is None and ctx.env.options.apply_query_rewrites: schema_deflt = ptrcls.get_schema_reflection_default(ctx.env.schema) if schema_deflt is not None: assert isinstance(ptrcls, s_pointers.Pointer) ptrcls_n = ptrcls.get_shortname(ctx.env.schema).name schema_qlexpr = qlast.BinOp( left=qlast.Path(steps=[ qlast.Source(), qlast.Ptr( ptr=qlast.ObjectRef(name=ptrcls_n), direction=s_pointers.PointerDirection.Outbound, type=('property' if ptrcls.is_link_property( ctx.env.schema) else None)) ], ), right=qlparser.parse_fragment(schema_deflt), op='??', ) if schema_qlexpr is None: if comp_expr is None: ptrcls_sn = ptrcls.get_shortname(ctx.env.schema) raise errors.InternalServerError( f'{ptrcls_sn!r} is not a computable pointer') comp_qlexpr = qlparser.parse(comp_expr.text) assert isinstance(comp_qlexpr, qlast.Expr), 'expected qlast.Expr' schema_qlexpr = comp_qlexpr # 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) assert target_scls is not None if not target_scls.is_object_type(): schema_qlexpr = qlast.TypeCast( type=typegen.type_to_ql_typeref(target_scls, ctx=ctx), expr=schema_qlexpr, ) qlexpr = astutils.ensure_qlstmt(schema_qlexpr) qlctx = None inner_source_path_id = None path_id_ns = None newctx: Callable[[], ContextManager[context.ContextLevel]] 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: src_path = rptr.target.path_id.src_path() assert src_path is not None source_path_id = src_path result_path_id = pathctx.extend_path_id( source_path_id, ptrcls=ptrcls, ns=ctx.path_id_namespace, ctx=ctx, ) result_stype = ptrcls.get_target(ctx.env.schema) base_object = ctx.env.schema.get('std::BaseObject', type=s_types.Type) with newctx() as subctx: subctx.disable_shadowing.add(ptrcls) if result_stype != base_object: subctx.view_scls = result_stype subctx.view_rptr = context.ViewRPtr(source_scls, ptrcls=ptrcls, rptr=rptr) # type: ignore subctx.anchors[qlast.Source().name] = source_set subctx.empty_result_type_hint = ptrcls.get_target(ctx.env.schema) subctx.partial_path_prefix = source_set # On a mutation, make the expr_exposed. This corresponds with # a similar check on is_mutation in _normalize_view_ptr_expr. if (source_scls.get_expr_type(ctx.env.schema) != s_types.ExprType.Select): subctx.expr_exposed = True if isinstance(qlexpr, qlast.Statement): subctx.stmt_metadata[qlexpr] = context.StatementMetadata( is_unnest_fence=unnest_fence, iterator_target=True, ) comp_ir_set = ensure_set(dispatch.compile(qlexpr, ctx=subctx), ctx=subctx) comp_ir_set = new_set_from_set(comp_ir_set, path_id=result_path_id, rptr=rptr, context=srcctx, ctx=ctx) rptr.target = comp_ir_set return comp_ir_set
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 _get_shape_configuration( ir_set: irast.Set, *, rptr: Optional[irast.Pointer]=None, parent_view_type: Optional[s_types.ExprType]=None, ctx: context.ContextLevel ) -> List[Tuple[irast.Set, s_pointers.Pointer, qlast.ShapeOp]]: """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: List[ Union[s_types.Type, s_pointers.PointerLike]] = [] 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 = typegen.ptrcls_from_ptrref(rptr.ptrref, ctx=ctx) 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 = [] for source in sources: for ptr, shape_op 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, shape_op)) if is_objtype: assert isinstance(stype, s_objtypes.ObjectType) view_type = stype.get_expr_type(ctx.env.schema) is_mutation = view_type in (s_types.ExprType.Insert, s_types.ExprType.Update) is_parent_update = parent_view_type is s_types.ExprType.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) view_shape = ctx.env.view_shapes[stype] view_shape_ptrs = {p for p, _ in view_shape} for ptr in pointers: if ptr.is_id_pointer(ctx.env.schema): if ptr not in view_shape_ptrs: shape_metadata = ctx.env.view_shapes_metadata[stype] view_shape.insert(0, (ptr, qlast.ShapeOp.ASSIGN)) shape_metadata.has_implicit_id = True shape_ptrs.insert( 0, (ir_set, ptr, qlast.ShapeOp.ASSIGN)) break is_mutation = parent_view_type in { s_types.ExprType.Insert, s_types.ExprType.Update } implicit_tid = ( stype is not None and has_implicit_tid(stype, is_mutation=is_mutation, ctx=ctx) ) if implicit_tid: assert isinstance(stype, s_objtypes.ObjectType) try: ptr = setgen.resolve_ptr(stype, '__tid__', ctx=ctx) except errors.InvalidReferenceError: 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().name] = ir_set ptr = _normalize_view_ptr_expr( ql, stype, path_id=ir_set.path_id, ctx=scopectx) view_shape = ctx.env.view_shapes[stype] view_shape_ptrs = {p for p, _ in view_shape} if ptr not in view_shape_ptrs: view_shape.insert(0, (ptr, qlast.ShapeOp.ASSIGN)) shape_ptrs.insert(0, (ir_set, ptr, qlast.ShapeOp.ASSIGN)) return shape_ptrs
def _process_view(*, stype: s_nodes.Node, path_id: irast.PathId, path_id_namespace: typing.Optional[ irast.WeakNamespace] = None, elements: typing.List[qlast.ShapeElement], view_rptr: typing.Optional[context.ViewRPtr] = None, view_name: typing.Optional[sn.SchemaName] = None, is_insert: bool = False, is_update: bool = False, ctx: context.CompilerContext) -> s_nodes.Node: view_scls = schemactx.derive_view(stype, is_insert=is_insert, is_update=is_update, derived_name=view_name, ctx=ctx) 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: pointers.append( _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 is_insert: explicit_ptrs = { ptrcls.get_shortname(ctx.env.schema).name 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 if not ptrcls.get_default(ctx.env.schema): if ptrcls.get_required(ctx.env.schema): if ptrcls.is_property(ctx.env.schema): what = 'property' else: what = 'link' raise errors.MissingRequiredError( f'missing value for required {what} ' f'{stype.get_displayname(ctx.env.schema)}.' f'{ptrcls.get_displayname(ctx.env.schema)}') 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)) ])) 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, view_rptr=view_rptr, ctx=scopectx)) for ptrcls in pointers: if ptrcls.is_link_property(ctx.env.schema): source = view_rptr.ptrcls else: source = view_scls if is_defining_shape: ctx.env.view_shapes[source].append(ptrcls) if (view_rptr is not None and view_rptr.ptrcls is not None and view_scls is not stype): ctx.env.schema = view_scls.set_field_value(ctx.env.schema, 'rptr', view_rptr.ptrcls) return view_scls
def _cast_array(ir_set: irast.Set, orig_stype: s_types.Type, new_stype: s_types.Type, *, srcctx: Optional[parsing.ParserContext], ctx: context.ContextLevel) -> irast.Set: assert isinstance(orig_stype, s_types.Array) direct_cast = _find_cast(orig_stype, new_stype, srcctx=srcctx, ctx=ctx) if direct_cast is None: if not new_stype.is_array(): raise errors.QueryError( f'cannot cast {orig_stype.get_displayname(ctx.env.schema)!r} ' f'to {new_stype.get_displayname(ctx.env.schema)!r}', context=srcctx) assert isinstance(new_stype, s_types.Array) el_type = new_stype.get_subtypes(ctx.env.schema)[0] else: el_type = new_stype orig_el_type = orig_stype.get_subtypes(ctx.env.schema)[0] el_cast = _find_cast(orig_el_type, el_type, srcctx=srcctx, ctx=ctx) if el_cast is not None and el_cast.get_from_cast(ctx.env.schema): # Simple cast return _cast_to_ir(ir_set, el_cast, orig_stype, new_stype, ctx=ctx) else: pathctx.register_set_in_scope(ir_set, ctx=ctx) with ctx.new() as subctx: subctx.anchors = subctx.anchors.copy() source_alias = subctx.aliases.get('a') subctx.anchors[source_alias] = ir_set unpacked = qlast.FunctionCall( func=('__std__', 'array_unpack'), args=[ qlast.Path(steps=[qlast.ObjectRef(name=source_alias)], ), ], ) enumerated = setgen.ensure_set( dispatch.compile( qlast.FunctionCall( func=('__std__', 'enumerate'), args=[unpacked], ), ctx=subctx, ), ctx=subctx, ) enumerated_alias = subctx.aliases.get('e') subctx.anchors[enumerated_alias] = enumerated enumerated_ref = qlast.Path( steps=[qlast.ObjectRef(name=enumerated_alias)], ) elements = qlast.FunctionCall( func=('__std__', 'array_agg'), args=[ qlast.SelectQuery( result=qlast.TypeCast( expr=qlast.Path(steps=[ enumerated_ref, qlast.Ptr(ptr=qlast.ObjectRef( name='1', direction='>', ), ), ], ), type=typegen.type_to_ql_typeref( el_type, ctx=subctx, ), cardinality_mod=qlast.CardinalityModifier.Required, ), orderby=[ qlast.SortExpr( path=qlast.Path(steps=[ enumerated_ref, qlast.Ptr(ptr=qlast.ObjectRef( name='0', direction='>', ), ), ], ), direction=qlast.SortOrder.Asc, ), ], ), ], ) array_ir = dispatch.compile(elements, ctx=subctx) assert isinstance(array_ir, irast.Set) if direct_cast is not None: ctx.env.schema, array_stype = s_types.Array.from_subtypes( ctx.env.schema, [el_type]) return _cast_to_ir(array_ir, direct_cast, array_stype, new_stype, ctx=ctx) else: return array_ir
# 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), ) # Produce a query that finds the conflicting objects select_ast = qlast.DetachedExpr( expr=qlast.SelectQuery(result=insert_subject, where=cond) ) return select_ast
def _process_view(*, stype: s_nodes.Node, path_id: irast.PathId, path_id_namespace: typing.Optional[ irast.WeakNamespace] = None, elements: typing.List[qlast.ShapeElement], view_rptr: typing.Optional[context.ViewRPtr] = None, view_name: typing.Optional[sn.SchemaName] = None, is_insert: bool = False, is_update: bool = False, ctx: context.CompilerContext) -> s_nodes.Node: view_scls = schemactx.derive_view(stype, is_insert=is_insert, is_update=is_update, derived_name=view_name, ctx=ctx) is_mutation = is_insert or is_update is_defining_shape = ctx.expr_exposed or is_mutation pointers = [] for shape_el in elements: with ctx.newscope(fenced=True) as scopectx: pointers.append( _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 is_insert: explicit_ptrs = { ptrcls.get_shortname(ctx.env.schema).name 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 if not ptrcls.get_default(ctx.env.schema): if ptrcls.get_required(ctx.env.schema): if ptrcls.is_property(ctx.env.schema): what = 'property' else: what = 'link' raise errors.MissingRequiredError( f'missing value for required {what} ' f'{stype.get_displayname(ctx.env.schema)}.' f'{ptrcls.get_displayname(ctx.env.schema)}') 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)) ])) 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, view_rptr=view_rptr, ctx=scopectx)) for ptrcls in pointers: if ptrcls.is_link_property(ctx.env.schema): source = view_rptr.derived_ptrcls else: source = view_scls if ptrcls.get_source(ctx.env.schema) is source: ctx.env.schema = source.add_pointer(ctx.env.schema, ptrcls, replace=True) if is_defining_shape: if source is None: # The nested shape is merely selecting the pointer, # so the link class has not been derived. But for # the purposes of shape tracking, we must derive it # still. The derived pointer must be treated the same # as the original, as this is not a new computable, # and both `Foo.ptr` and `Foo { ptr }` are the same path, # hence the `transparent` modifier. source = derive_ptrcls(view_rptr, target_scls=view_scls, transparent=True, ctx=ctx) ctx.env.view_shapes[source].append(ptrcls) if (view_rptr is not None and view_rptr.derived_ptrcls is not None and view_scls is not stype): ctx.env.schema = view_scls.set_field_value(ctx.env.schema, 'rptr', view_rptr.derived_ptrcls) return view_scls
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_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, ))