def get_scope_stmt(path_id: irast.PathId, *, ctx: context.CompilerContextLevel) -> pgast.Query: stmt = ctx.path_scope.get(path_id) if stmt is None and path_id.is_ptr_path(): stmt = ctx.path_scope.get(path_id.tgt_path()) if stmt is None: raise LookupError(f'cannot find scope statement for {path_id}') return stmt
def get_less_specific_aspect(path_id: irast.PathId, aspect: str): if path_id.is_objtype_path(): mapping = OBJECT_ASPECT_SPECIFICITY_MAP else: mapping = PRIMITIVE_ASPECT_SPECIFICITY_MAP return mapping.get(aspect)
def include_rvar(stmt: pgast.Query, rvar: pgast.BaseRangeVar, path_id: irast.PathId, *, overwrite_path_rvar: bool = False, ctx: context.CompilerContextLevel) -> pgast.BaseRangeVar: """Ensure that *rvar* is visible in *stmt* as a value/source aspect. :param stmt: The statement to include *rel* in. :param rvar: The range var node to join. :param join_type: JOIN type to use when including *rel*. :param aspect: The reference aspect of the range var. :param ctx: Compiler context. """ if path_id.is_objtype_path(): aspects = ['source', 'value'] else: aspects = ['value'] return include_specific_rvar(stmt, rvar=rvar, path_id=path_id, overwrite_path_rvar=overwrite_path_rvar, aspects=aspects, ctx=ctx)
def reverse_map_path_id( path_id: irast.PathId, path_id_map: typing.Dict[irast.PathId, irast.PathId]) -> irast.PathId: for outer_id, inner_id in path_id_map.items(): new_path_id = path_id.replace_prefix(inner_id, outer_id) if new_path_id != path_id: path_id = new_path_id break return path_id
def _get_rel_path_output(rel: pgast.BaseRelation, path_id: irast.PathId, *, aspect: str, ptr_info: typing.Optional[ pg_types.PointerStorageInfo] = None, env: context.Environment) -> pgast.OutputVar: if path_id.is_objtype_path(): if aspect == 'identity': aspect = 'value' if aspect != 'value': raise LookupError( f'invalid request for non-scalar path {path_id} {aspect}') if (path_id == rel.path_id or (rel.path_id.is_type_indirection_path() and path_id == rel.path_id.src_path())): path_id = irutils.get_id_path_id(path_id, schema=env.schema) else: if aspect == 'identity': raise LookupError( f'invalid request for scalar path {path_id} {aspect}') if path_id.rptr_dir() != s_pointers.PointerDirection.Outbound: raise LookupError( f'{path_id} is an inbound pointer and cannot be resolved ' f'on a base relation') ptrcls = path_id.rptr() if ptrcls is None: raise ValueError( f'could not resolve trailing pointer class for {path_id}') ptr_info = pg_types.get_pointer_storage_info(ptrcls, resolve_type=False, link_bias=False) result = pgast.ColumnRef(name=[ptr_info.column_name], nullable=rel.nullable or not ptrcls.required) rel.path_outputs[path_id, aspect] = result return result
def get_path_output_alias(path_id: irast.PathId, aspect: str, *, env: context.Environment) -> str: rptr = path_id.rptr() if rptr is not None: ptrname = rptr.shortname alias_base = ptrname.name elif isinstance(path_id.target, s_types.Collection): alias_base = path_id.target.schema_name else: alias_base = path_id.target.name.name return env.aliases.get(f'{alias_base}_{aspect}')
def include_rvar( stmt: pgast.Query, rvar: pgast.BaseRangeVar, path_id: irast.PathId, *, overwrite_path_rvar: bool=False, pull_namespace: bool=True, aspects: typing.Optional[typing.Iterable[str]]=None, ctx: context.CompilerContextLevel) -> pgast.BaseRangeVar: """Ensure that *rvar* is visible in *stmt* as a value/source aspect. :param stmt: The statement to include *rel* in. :param rvar: The range var node to join. :param join_type: JOIN type to use when including *rel*. :param aspect: The reference aspect of the range var. :param ctx: Compiler context. """ if aspects is None: if path_id.is_objtype_path(): aspects = ('source', 'value') else: aspects = ('value',) return include_specific_rvar( stmt, rvar=rvar, path_id=path_id, overwrite_path_rvar=overwrite_path_rvar, pull_namespace=pull_namespace, aspects=aspects, ctx=ctx)
def get_path_var(rel: pgast.BaseRelation, path_id: irast.PathId, *, aspect: str, env: context.Environment) -> pgast.OutputVar: """Return an OutputVar for a given *path_id* in a given *rel*.""" if isinstance(rel, pgast.CommonTableExpr): rel = rel.query # Check if we already have a var, before remapping the path_id. # This is useful for serialized aspect disambiguation in tuples, # since process_set_as_tuple() records serialized vars with # original path_id. if (path_id, aspect) in rel.path_namespace: return rel.path_namespace[path_id, aspect] if rel.view_path_id_map: path_id = map_path_id(path_id, rel.view_path_id_map) if (path_id, aspect) in rel.path_namespace: return rel.path_namespace[path_id, aspect] ptrcls = path_id.rptr() if ptrcls is not None: ptr_info = pg_types.get_pointer_storage_info(ptrcls, resolve_type=False, link_bias=False) ptr_dir = path_id.rptr_dir() is_inbound = ptr_dir == s_pointers.PointerDirection.Inbound if is_inbound: src_path_id = path_id else: src_path_id = path_id.src_path() else: ptr_info = None src_path_id = None ptr_dir = None if astutils.is_set_op_query(rel): cb = functools.partial(get_path_output_or_null, env=env, path_id=path_id, aspect=aspect) outputs = astutils.for_each_query_in_set(rel, cb) first = None optional = False all_null = True nullable = False for colref, is_null in outputs: if colref.nullable: nullable = True if first is None: first = colref if is_null: optional = True else: all_null = False if all_null: raise LookupError(f'cannot find refs for ' f'path {path_id} {aspect} in {rel}') # Path vars produced by UNION expressions can be "optional", # i.e the record is accepted as-is when such var is NULL. # This is necessary to correctly join heterogeneous UNIONs. var = dbobj.get_rvar_var(None, first, optional=optional, nullable=optional or nullable) put_path_var(rel, path_id, var, aspect=aspect, env=env) return var if ptrcls is None: if len(path_id) == 1: # This is an scalar set derived from an expression. src_path_id = path_id elif ptrcls.is_link_property(): if ptr_info.table_type != 'link' and not is_inbound: # This is a link prop that is stored in source rel, # step back to link source rvar. src_path_id = path_id.src_path().src_path() elif ptr_info.table_type != 'ObjectType' and not is_inbound: # Ref is in the mapping rvar. src_path_id = path_id.ptr_path() rel_rvar = maybe_get_path_rvar(rel, path_id, aspect=aspect, env=env) if rel_rvar is None: alt_aspect = get_less_specific_aspect(path_id, aspect) if alt_aspect is not None: rel_rvar = maybe_get_path_rvar(rel, path_id, aspect=alt_aspect, env=env) else: alt_aspect = None if rel_rvar is None: if src_path_id.is_objtype_path(): if aspect == 'identity': src_aspect = 'value' else: src_aspect = 'source' else: src_aspect = aspect if isinstance(src_path_id.rptr(), irutils.TupleIndirectionLink): rel_rvar = maybe_get_path_rvar(rel, src_path_id, aspect=src_aspect, env=env) if rel_rvar is None: rel_rvar = maybe_get_path_rvar(rel, src_path_id.src_path(), aspect=src_aspect, env=env) else: rel_rvar = maybe_get_path_rvar(rel, src_path_id, aspect=src_aspect, env=env) if src_aspect != 'source' and path_id != src_path_id: rel_rvar = maybe_get_path_rvar(rel, src_path_id, aspect='source', env=env) if rel_rvar is None and alt_aspect is not None: # There is no source range var for the requested aspect, # check if there is a cached var with less specificity. var = rel.path_namespace.get((path_id, alt_aspect)) if var is not None: put_path_var(rel, path_id, var, aspect=aspect, env=env) return var if rel_rvar is None: raise LookupError(f'there is no range var for ' f'{src_path_id} {src_aspect} in {rel}') source_rel = rel_rvar.query drilldown_path_id = map_path_id(path_id, rel.view_path_id_map) if source_rel in env.root_rels and len(source_rel.path_scope) == 1: if not drilldown_path_id.is_objtype_path() and ptrcls is not None: outer_path_id = drilldown_path_id.src_path() else: outer_path_id = drilldown_path_id path_id_map = {outer_path_id: next(iter(source_rel.path_scope))} drilldown_path_id = map_path_id(drilldown_path_id, path_id_map) outvar = get_path_output(source_rel, drilldown_path_id, ptr_info=ptr_info, aspect=aspect, env=env) var = dbobj.get_rvar_var(rel_rvar, outvar) put_path_var(rel, path_id, var, aspect=aspect, env=env) if isinstance(var, pgast.TupleVar): for element in var.elements: put_path_var(rel, element.path_id, element.val, aspect=aspect, env=env) return var
def _get_rel_path_output(rel: pgast.BaseRelation, path_id: irast.PathId, *, aspect: str, ptr_info: typing.Optional[ pg_types.PointerStorageInfo] = None, env: context.Environment) -> pgast.OutputVar: if path_id.is_objtype_path(): if aspect == 'identity': aspect = 'value' if aspect != 'value': raise LookupError( f'invalid request for non-scalar path {path_id} {aspect}') if (path_id == rel.path_id or (rel.path_id.is_type_indirection_path() and path_id == rel.path_id.src_path())): path_id = irutils.get_id_path_id(path_id, schema=env.schema) else: if aspect == 'identity': raise LookupError( f'invalid request for scalar path {path_id} {aspect}') elif aspect == 'serialized': aspect = 'value' var = rel.path_outputs.get((path_id, aspect)) if var is not None: return var ptrcls = path_id.rptr() rptr_dir = path_id.rptr_dir() if (rptr_dir is not None and rptr_dir != s_pointers.PointerDirection.Outbound): raise LookupError( f'{path_id} is an inbound pointer and cannot be resolved ' f'on a base relation') if isinstance(rel, pgast.NullRelation): if ptrcls is not None: target = ptrcls.target else: target = path_id.target if ptr_info is not None: name = ptr_info.column_name else: name = env.aliases.get('v') val = typecomp.cast(pgast.Constant(val=None, nullable=True), source_type=target, target_type=target, force=True, env=env) rel.target_list.append(pgast.ResTarget(name=name, val=val)) result = pgast.ColumnRef(name=[name], nullable=True) else: if ptrcls is None: raise ValueError( f'could not resolve trailing pointer class for {path_id}') ptr_info = pg_types.get_pointer_storage_info(ptrcls, resolve_type=False, link_bias=False) result = pgast.ColumnRef(name=[ptr_info.column_name], nullable=not ptrcls.required) rel.path_outputs[path_id, aspect] = result return result
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