def visit_Set(self, node): if node.expr is not None: result = self.visit(node.expr) else: links = [] while node.rptr and (not node.show_as_anchor or self.context.inline_anchors): rptr = node.rptr ptrcls = rptr.ptrcls pname = ptrcls.shortname if isinstance(rptr.target.scls, s_objtypes.ObjectType): target = rptr.target.scls.shortname target = qlast.TypeName(maintype=qlast.ObjectRef( name=target.name, module=target.module)) else: target = None link = qlast.Ptr( ptr=qlast.ObjectRef(name=pname.name, ), direction=rptr.direction, target=target, ) if isinstance(ptrcls.source, s_links.Link): link.type = 'property' links.append(link) node = node.rptr.source result = qlast.Path() 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: step = qlast.ObjectRef(name=node.scls.shortname.name, module=node.scls.shortname.module) result.steps.append(step) result.steps.extend(reversed(links)) if node.shape: result = qlast.Shape(expr=result, elements=[]) for el in node.shape: rptr = el.rptr ptrcls = rptr.ptrcls pn = ptrcls.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 visit_order(self, node): # if there is no specific ordering, then order by id if not node.value: 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.value: 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 reduce_Expr_PathStep(self, *kids): path = kids[0].val if not isinstance(path, qlast.Path): path = qlast.Path(steps=[path]) path.steps.append(kids[1].val) self.val = path
def reduce_NodeName_Shape(self, *kids): self.val = qlast.Shape(expr=qlast.Path(steps=[ qlast.ObjectRef(name=kids[0].val.name, module=kids[0].val.module, context=kids[0].context) ]), elements=kids[1].val)
def reduce_PathStepName(self, *kids): from edb.lang.schema import pointers as s_pointers self.val = qlast.Path(steps=[ qlast.Ptr(ptr=kids[0].val, direction=s_pointers.PointerDirection.Outbound) ])
def reduce_Expr_DOT_FCONST(self, *kids): # this is a valid link-like syntax for accessing unnamed tuples path = kids[0].val if not isinstance(path, qlast.Path): path = qlast.Path(steps=[path]) path.steps.extend(self._float_to_path(kids[2], kids[1].context)) self.val = path
def reduce_AT_ShortNodeName(self, *kids): self.val = qlast.Path( steps=[ qlast.Ptr( ptr=kids[1].val, type='property' ) ] )
def visit_TypeRef(self, node): # Bare TypeRef only appears as rhs of IS [NOT] and is always # an object type reference. mtn = node.maintype result = qlast.Path( steps=[qlast.ObjectRef(module=mtn.module, name=mtn.name)]) return result
def _visit_query(self, node): # populate input variables with defaults, where applicable if node.variables: self.visit(node.variables) # base Query needs to be configured specially base = self._context.gqlcore.get('Query') # special treatment of the selection_set, different from inner # recursion query = qlast.SelectQuery(result=qlast.Shape(expr=qlast.Path( steps=[qlast.ObjectRef(name='Query', module='stdgraphql')]), elements=[]), ) self._context.fields.append({}) self._context.path.append([Step(None, base)]) query.result.elements = self.visit(node.selection_set) self._context.fields.pop() self._context.path.pop() return query
def visit_ObjectField(self, node): fname = node.name # handle boolean ops if fname == 'and': return self._visit_list_of_inputs(node.value.value, ast.ops.AND) elif fname == 'or': return self._visit_list_of_inputs(node.value.value, ast.ops.OR) elif fname == 'not': return qlast.UnaryOp(op=ast.ops.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) # potentially need to cast the 'name' side into a <str>, so as # to be compatible with the 'value' typename = target.get_field_type(fname).short_name if (typename != 'str' and gt.EDB_TO_GQL_SCALARS_MAP[typename] in {GraphQLString, GraphQLID}): name = qlast.TypeCast( expr=name, type=qlast.TypeName(maintype=qlast.ObjectRef(name='str')), ) self._context.filter = name return self.visit(node.value)
def compile_result_clause( result: qlast.Base, *, view_scls: typing.Optional[s_types.Type]=None, view_rptr: typing.Optional[context.ViewRPtr]=None, view_name: typing.Optional[s_name.SchemaName]=None, result_alias: typing.Optional[str]=None, ctx: context.ContextLevel) -> irast.Set: with ctx.new() as sctx: sctx.clause = 'result' if sctx.stmt is ctx.toplevel_stmt: sctx.toplevel_clause = sctx.clause sctx.expr_exposed = True if isinstance(result, qlast.Shape): result_expr = result.expr shape = result.elements else: result_expr = result shape = None if result_alias: stmtctx.declare_view(result_expr, alias=result_alias, ctx=sctx) result_expr = qlast.Path( steps=[qlast.ObjectRef(name=result_alias)] ) expr = setgen.ensure_set( dispatch.compile(result_expr, ctx=sctx), ctx=sctx) 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) ctx.partial_path_prefix = result return result
def visit_Argument(self, node, *, get_path_prefix): op = ast.ops.EQ name_parts = node.name _, target = self._get_parent_and_current_type() name = get_path_prefix() name.append(qlast.Ptr(ptr=qlast.ObjectRef(name=name_parts))) name = qlast.Path(steps=name) value = self.visit(node.value) # potentially need to cast the 'name' side into a <str>, so as # to be compatible with the 'value' typename = target.get_field_type(name_parts).short_name if (typename != 'str' and gt.EDB_TO_GQL_SCALARS_MAP[typename] in {GraphQLString, GraphQLID}): name = qlast.TypeCast( expr=name, type=qlast.TypeName(maintype=qlast.ObjectRef(name='str')), ) return qlast.BinOp(left=name, op=op, right=value)
def reduce_NodeName(self, *kids): self.val = qlast.Path( steps=[qlast.ObjectRef(name=kids[0].val.name, module=kids[0].val.module)])
def reduce_DUNDERSUBJECT(self, *kids): self.val = qlast.Path(steps=[qlast.Subject()])
def reduce_DUNDERSOURCE(self, *kids): self.val = qlast.Path(steps=[qlast.Source()])
def reduce_Identifier(self, *kids): self.val = qlast.Path(steps=[qlast.ObjectRef(name=kids[0].val)])
def reduce_DOT_FCONST(self, *kids): # this is a valid link-like syntax for accessing unnamed tuples self.val = qlast.Path( steps=self._float_to_path(kids[1], kids[0].context), partial=True)
def _normalize_view_ptr_expr( shape_el: qlast.ShapeElement, view_scls: s_nodes.Node, *, path_id: irast.PathId, is_insert: bool = False, is_update: bool = False, view_rptr: typing.Optional[context.ViewRPtr] = None, ctx: context.CompilerContext) -> s_pointers.Pointer: steps = shape_el.expr.steps is_linkprop = False is_mutation = is_insert or is_update # Pointers may be qualified by the explicit source # class, which is equivalent to Expr[IS Type]. is_polymorphic = len(steps) == 2 scls = view_scls.peel_view() ptrsource = scls qlexpr = None if is_polymorphic: source = qlast.TypeFilter(expr=qlast.Path(steps=[qlast.Source()]), type=qlast.TypeName(maintype=steps[0])) lexpr = steps[1] ptrsource = schemactx.get_schema_type(steps[0], ctx=ctx) elif len(steps) == 1: lexpr = steps[0] is_linkprop = lexpr.type == 'property' if is_linkprop: if view_rptr is None: raise errors.EdgeQLError( 'invalid reference to link property ' 'in top level shape', context=lexpr.context) if view_rptr.ptrcls is None: derive_ptrcls(view_rptr, target_scls=view_scls, ctx=ctx) ptrsource = scls = view_rptr.ptrcls source = qlast.Source() else: raise RuntimeError( f'unexpected path length in view shape: {len(steps)}') ptrname = (lexpr.ptr.module, lexpr.ptr.name) ptrcls_is_derived = False compexpr = shape_el.compexpr if compexpr is None and is_insert and shape_el.elements: # Nested insert short form: # INSERT Foo { bar: Spam { name := 'name' }} # Expand to: # INSERT Foo { bar := (INSERT Spam { name := 'name' }) } if lexpr.target is not None: ptr_target = schemactx.get_schema_type(lexpr.target, ctx=ctx) else: ptr_target = None base_ptrcls = ptrcls = setgen.resolve_ptr( ptrsource, ptrname, s_pointers.PointerDirection.Outbound, target=ptr_target, ctx=ctx) compexpr = qlast.InsertQuery(subject=qlast.Path(steps=[ qlast.ObjectRef(name=ptrcls.target.name.name, module=ptrcls.target.name.module) ]), shape=shape_el.elements) if compexpr is None: if lexpr.target is not None: ptr_target = schemactx.get_schema_type(lexpr.target, ctx=ctx) else: ptr_target = None base_ptrcls = ptrcls = setgen.resolve_ptr( ptrsource, ptrname, s_pointers.PointerDirection.Outbound, target=ptr_target, ctx=ctx) base_ptr_is_computable = ptrcls in ctx.source_map if ptr_target is not None and ptr_target != base_ptrcls.target: # This happens when a union type target is narrowed by an # [IS Type] construct. Since the derived pointer will have # the correct target, we don't need to do anything, but # remove the [IS] qualifier to prevent recursion. lexpr.target = None else: ptr_target = ptrcls.target if ptrcls in ctx.pending_cardinality: # We do not know the parent's pointer cardinality yet. ptr_cardinality = None else: ptr_cardinality = ptrcls.cardinality if shape_el.elements: sub_view_rptr = context.ViewRPtr( ptrsource if is_linkprop else view_scls, ptrcls=ptrcls, is_insert=is_insert, is_update=is_update) sub_path_id = path_id.extend(ptrcls, target=ptrcls.target) ctx.path_scope.attach_path(sub_path_id) if is_update: for subel in shape_el.elements or []: is_prop = (isinstance(subel.expr.steps[0], qlast.Ptr) and subel.expr.steps[0].type == 'property') if not is_prop: raise errors.EdgeQLError( 'only references to link properties are allowed ' 'in nested UPDATE shapes', context=subel.context) ptr_target = _process_view(scls=ptr_target, path_id=sub_path_id, view_rptr=sub_view_rptr, elements=shape_el.elements, is_update=True, ctx=ctx) else: ptr_target = _process_view(scls=ptr_target, path_id=sub_path_id, view_rptr=sub_view_rptr, elements=shape_el.elements, ctx=ctx) ptrcls = sub_view_rptr.derived_ptrcls if ptrcls is None: ptrcls_is_derived = False ptrcls = sub_view_rptr.ptrcls else: ptrcls_is_derived = True if (shape_el.where or shape_el.orderby or shape_el.offset or shape_el.limit or base_ptr_is_computable or is_polymorphic): if qlexpr is None: qlexpr = qlast.Path(steps=[source, lexpr]) qlexpr = astutils.ensure_qlstmt(qlexpr) qlexpr.where = shape_el.where qlexpr.orderby = shape_el.orderby qlexpr.offset = shape_el.offset qlexpr.limit = shape_el.limit else: try: base_ptrcls = ptrcls = setgen.resolve_ptr( ptrsource, ptrname, s_pointers.PointerDirection.Outbound, ctx=ctx) ptr_name = ptrcls.shortname except errors.EdgeQLReferenceError: if is_mutation: raise base_ptrcls = ptrcls = None ptr_module = (ptrname[0] or ctx.derived_target_module or scls.name.module) ptr_name = sn.SchemaName(module=ptr_module, name=ptrname[1]) qlexpr = astutils.ensure_qlstmt(compexpr) with ctx.newscope(fenced=True) as shape_expr_ctx: # Put current pointer class in context, so # that references to link properties in sub-SELECT # can be resolved. This is necessary for proper # evaluation of link properties on computable links, # most importantly, in INSERT/UPDATE context. shape_expr_ctx.view_rptr = context.ViewRPtr( ptrsource if is_linkprop else view_scls, ptrcls=ptrcls, ptrcls_name=ptr_name, ptrcls_is_linkprop=is_linkprop, is_insert=is_insert, is_update=is_update) shape_expr_ctx.path_scope.unnest_fence = True if is_mutation: shape_expr_ctx.expr_exposed = True irexpr = dispatch.compile(qlexpr, ctx=shape_expr_ctx) irexpr.context = compexpr.context if base_ptrcls is None: base_ptrcls = ptrcls = shape_expr_ctx.view_rptr.ptrcls derived_ptrcls = shape_expr_ctx.view_rptr.derived_ptrcls if derived_ptrcls is not None: ptrcls_is_derived = True ptrcls = derived_ptrcls ptr_cardinality = None ptr_target = irutils.infer_type(irexpr, ctx.schema) if ptr_target is None: msg = 'cannot determine expression result type' raise errors.EdgeQLError(msg, context=shape_el.context) if is_mutation and not ptr_target.assignment_castable_to( base_ptrcls.target, schema=ctx.schema): # Validate that the insert/update expression is # of the correct class. lname = f'({ptrsource.name}).{ptrcls.shortname.name}' expected = [repr(str(base_ptrcls.target.name))] raise edgedb_error.InvalidPointerTargetError( f'invalid target for link {str(lname)!r}: ' f'{str(ptr_target.name)!r} (expecting ' f'{" or ".join(expected)})') if qlexpr is not None or ptr_target is not ptrcls.target: if not ptrcls_is_derived: if is_linkprop: rptrcls = view_rptr.derived_ptrcls if rptrcls is None: rptrcls = derive_ptrcls(view_rptr, target_scls=view_scls, ctx=ctx) src_scls = rptrcls else: src_scls = view_scls ptrcls = schemactx.derive_view(ptrcls, src_scls, ptr_target, is_insert=is_insert, is_update=is_update, derived_name_quals=[view_scls.name], ctx=ctx) if qlexpr is not None: ctx.source_map[ptrcls] = (qlexpr, ctx) ptrcls.computable = True if not is_mutation: if ptr_cardinality is None: if compexpr is not None: ctx.pending_cardinality.add(ptrcls) elif ptrcls is not base_ptrcls: ctx.pointer_derivation_map[base_ptrcls].append(ptrcls) ptrcls.cardinality = None else: ptrcls.cardinality = ptr_cardinality if ptrcls.is_protected_pointer() and qlexpr is not None: msg = f'cannot assign to {ptrcls.shortname.name}' raise errors.EdgeQLError(msg, context=shape_el.context) return ptrcls
def _process_view(*, scls: s_nodes.Node, path_id: irast.PathId, 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(scls, 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, is_insert=is_insert, is_update=is_update, view_rptr=view_rptr, ctx=scopectx)) if is_insert: explicit_ptrs = {ptrcls.shortname for ptrcls in pointers} for pn, ptrcls in scls.pointers.items(): if (not ptrcls.default or pn in explicit_ptrs or ptrcls.is_pure_computable()): continue default_ql = qlast.ShapeElement(expr=qlast.Path(steps=[ qlast.Ptr(ptr=qlast.ObjectRef(name=ptrcls.shortname.name, module=ptrcls.shortname.module)) ])) with ctx.newscope(fenced=True) as scopectx: scopectx.singletons = ctx.singletons.copy() scopectx.singletons.add(path_id) pointers.append( _normalize_view_ptr_expr(default_ql, view_scls, path_id=path_id, is_insert=is_insert, is_update=is_update, view_rptr=view_rptr, ctx=scopectx)) # Check if the view shape includes _only_ the link properties. # If so, we do not need to derive a new target view. lprops_only = True for ptrcls in pointers: if not ptrcls.is_link_property(): lprops_only = False break if lprops_only: view_scls = scls for ptrcls in pointers: if ptrcls.is_link_property(): source = view_rptr.derived_ptrcls else: source = view_scls if ptrcls.source is source and isinstance(source, s_sources.Source): # source may be an ScalarType in shapes that reference __type__, # hence the isinstance check. source.add_pointer(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. source = derive_ptrcls(view_rptr, target_scls=view_scls, ctx=ctx) ctx.class_shapes[source].append(ptrcls) if view_rptr is not None and view_rptr.derived_ptrcls is not None: view_scls.rptr = view_rptr.derived_ptrcls return view_scls
def visit_Field(self, node): if self._is_duplicate_field(node): return is_top, path, prevt, target, steps = \ self._prepare_field(node) json_mode = False # determine if there needs to be extra subqueries if not prevt.dummy and target.dummy: json_mode = True # this is a special introspection type eql, shape, filterable = target.get_template() spec = qlast.ShapeElement( expr=qlast.Path(steps=[ qlast.Ptr(ptr=qlast.ObjectRef( name=node.alias or node.name)) ]), compexpr=eql, ) elif prevt.is_field_shadowed(node.name): if prevt.has_native_field(node.name) and not node.alias: spec = filterable = shape = qlast.ShapeElement( expr=qlast.Path(steps=steps), ) else: prefix = qlast.Path(steps=self.get_path_prefix(-1)) eql, shape, filterable = prevt.get_field_template( node.name, parent=prefix, has_shape=bool(node.selection_set)) spec = qlast.ShapeElement( expr=qlast.Path(steps=[ qlast.Ptr(ptr=qlast.ObjectRef( # this is already a sub-query name=node.alias or node.name)) ]), compexpr=eql) else: # if the parent is NOT a shadowed type, we need an explicit SELECT eql, shape, filterable = target.get_template() spec = qlast.ShapeElement( expr=qlast.Path(steps=[ qlast.Ptr(ptr=qlast.ObjectRef( # this is already a sub-query name=node.alias or node.name)) ]), compexpr=eql) if node.selection_set is not None: if json_mode: pass else: # a single recursion target, so we can process # selection set now self._context.fields.append({}) vals = self.visit(node.selection_set) self._context.fields.pop() if shape: shape.elements = vals if filterable: where, orderby, offset, limit = \ self._visit_arguments(node.arguments) filterable.where = where filterable.orderby = orderby filterable.offset = offset filterable.limit = limit path.pop() return spec
def compile_func_to_ir(func, schema, *, anchors=None, security_context=None, modaliases=None, implicit_id_in_shapes=False): """Compile an EdgeQL function into EdgeDB IR.""" if debug.flags.edgeql_compile: debug.header('EdgeQL Function') debug.print(func.get_code(schema)) trees = ql_parser.parse_block(func.get_code(schema) + ';') if len(trees) != 1: raise errors.InvalidFunctionDefinitionError( 'functions can only contain one statement') tree = trees[0] if modaliases: ql_parser.append_module_aliases(tree, modaliases) if anchors is None: anchors = {} anchors['__defaults_mask__'] = irast.Parameter( name='__defaults_mask__', stype=schema.get('std::bytes')) func_params = func.get_params(schema) pg_params = s_func.PgParams.from_params(schema, func_params) for pi, p in enumerate(pg_params.params): p_shortname = p.get_shortname(schema) anchors[p_shortname] = irast.Parameter(name=p_shortname, stype=p.get_type(schema)) if p.get_default(schema) is None: continue tree.aliases.append( qlast.AliasedExpr( alias=p_shortname, expr=qlast. IfElse(condition=qlast.BinOp(left=qlast.FunctionCall( func=('std', 'bytes_get_bit'), args=[ qlast.FuncArg(arg=qlast.Path( steps=[qlast.ObjectRef( name='__defaults_mask__')])), qlast.FuncArg(arg=qlast.IntegerConstant(value=str(pi))) ]), right=qlast.IntegerConstant( value='0'), op='='), if_expr=qlast.Path( steps=[qlast.ObjectRef(name=p_shortname)]), else_expr=qlast._Optional( expr=p.get_ql_default(schema))))) ir = compile_ast_to_ir(tree, schema, anchors=anchors, func=func, security_context=security_context, modaliases=modaliases, implicit_id_in_shapes=implicit_id_in_shapes) return ir
def reduce_PathStep(self, *kids): self.val = qlast.Path(steps=[kids[0].val], partial=True)
def visit_Field(self, node): if self._is_duplicate_field(node): return is_top, path, prevt, target, steps = \ self._prepare_field(node) json_mode = False is_shadowed = prevt.is_field_shadowed(node.name) # determine if there needs to be extra subqueries if not prevt.dummy and target.dummy: json_mode = True # this is a special introspection type eql, shape, filterable = target.get_template() spec = qlast.ShapeElement( expr=qlast.Path(steps=[ qlast.Ptr(ptr=qlast.ObjectRef( name=node.alias or node.name)) ]), compexpr=eql, ) elif is_shadowed and not node.alias: # shadowed field that doesn't need an alias spec = filterable = shape = qlast.ShapeElement( expr=qlast.Path(steps=steps), ) elif not node.selection_set or is_shadowed and node.alias: # this is either an unshadowed terminal field or an aliased # shadowed field prefix = qlast.Path(steps=self.get_path_prefix(-1)) eql, shape, filterable = prevt.get_field_template( node.name, parent=prefix, has_shape=bool(node.selection_set)) spec = qlast.ShapeElement( expr=qlast.Path(steps=[ qlast.Ptr(ptr=qlast.ObjectRef( # this is already a sub-query name=node.alias or node.name)) ]), compexpr=eql) else: # if the parent is NOT a shadowed type, we need an explicit SELECT eql, shape, filterable = target.get_template() spec = qlast.ShapeElement( expr=qlast.Path(steps=[ qlast.Ptr(ptr=qlast.ObjectRef( # this is already a sub-query name=node.alias or node.name)) ]), compexpr=eql) if node.selection_set is not None: if json_mode: pass else: # a single recursion target, so we can process # selection set now self._context.fields.append({}) vals = self.visit(node.selection_set) self._context.fields.pop() if shape: shape.elements = vals if filterable: where, orderby, offset, limit = \ self._visit_arguments(node.arguments) filterable.where = where filterable.orderby = orderby filterable.offset = offset filterable.limit = limit # just to make sure that limit doesn't change the # serialization from list to a single object, we # need to force multi-cardinality, while being careful # as to not set the cardinality qualifier on a # non-computable shape element. if (limit is not None and (isinstance(filterable, qlast.Statement) or filterable.compexpr is not None)): spec.cardinality = qlast.Cardinality.MANY path.pop() return spec
def compile_result_clause( result: qlast.Base, *, view_scls: typing.Optional[s_types.Type]=None, view_rptr: typing.Optional[context.ViewRPtr]=None, view_name: typing.Optional[s_name.SchemaName]=None, result_alias: typing.Optional[str]=None, forward_rptr: bool=False, ctx: context.ContextLevel) -> irast.Set: with ctx.new() as sctx: sctx.clause = 'result' if sctx.stmt is ctx.toplevel_stmt: sctx.toplevel_clause = sctx.clause sctx.expr_exposed = True if forward_rptr: sctx.view_rptr = view_rptr # sctx.view_scls = view_scls 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 largely equivalent to # `WITH foo := expr SELECT foo` with one important exception: # the scope namespace does not get added to the current query # path scope. This is needed to handle FOR queries correctly. with sctx.newscope(temporary=True, fenced=True) as scopectx: stmtctx.declare_view( result_expr, alias=result_alias, temporary_scope=False, ctx=scopectx) 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.target if astutils.is_ql_empty_set(result_expr): expr = irutils.new_empty_set( sctx.schema, scls=target_t, alias=ctx.aliases.get('e')) 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 = irutils.new_empty_set( sctx.schema, scls=sctx.empty_result_type_hint, alias=ctx.aliases.get('e')) else: expr = setgen.ensure_set( dispatch.compile(result_expr, ctx=sctx), ctx=sctx) 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) ctx.partial_path_prefix = result return result
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 (not ptrcls.get_default(ctx.env.schema) or pn in explicit_ptrs or ptrcls.is_pure_computable(ctx.env.schema)): 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)) # Check if the view shape includes _only_ the link properties. # If so, we do not need to derive a new target view. lprops_only = True for ptrcls in pointers: if not ptrcls.is_link_property(ctx.env.schema): lprops_only = False break if lprops_only: view_scls = stype 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 and isinstance(source, s_sources.Source)): # source may be an ScalarType in shapes that reference __type__, # hence the isinstance check. 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.class_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