def get_rvar_path_var(rvar: pgast.PathRangeVar, path_id: irast.PathId, aspect: str, *, env: context.Environment) -> pgast.OutputVar: """Return ColumnRef for a given *path_id* in a given *range var*.""" if (path_id, aspect) in rvar.query.path_outputs: outvar = rvar.query.path_outputs[path_id, aspect] elif is_relation_rvar(rvar): if ((rptr := path_id.rptr()) is not None and rvar.typeref is not None and rvar.query.path_id != path_id and (not rvar.query.path_id.is_type_intersection_path() or rvar.query.path_id.src_path() != path_id)): actual_rptr = irtyputils.find_actual_ptrref( rvar.typeref, rptr, ) if actual_rptr is not None: ptr_si = pg_types.get_ptrref_storage_info(actual_rptr) else: ptr_si = None else: ptr_si = None outvar = _get_rel_path_output(rvar.query, path_id, ptr_info=ptr_si, aspect=aspect, env=env)
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 maybe_get_scope_stmt( path_id: irast.PathId, *, ctx: context.CompilerContextLevel, ) -> Optional[pgast.SelectStmt]: 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()) return stmt
def get_path_output_alias(path_id: irast.PathId, aspect: str, *, env: context.Environment) -> str: rptr = path_id.rptr() if rptr is not None: alias_base = rptr.shortname.name elif path_id.is_collection_path(): alias_base = path_id.target.collection else: _, _, alias_base = path_id.target_name_hint.rpartition('::') return env.aliases.get(f'{alias_base}_{aspect}')
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(PathAspect(aspect))
def serialize_expr_to_json( expr: pgast.BaseExpr, *, path_id: irast.PathId, nested: bool=False, env: context.Environment) -> pgast.BaseExpr: val: pgast.BaseExpr if isinstance(expr, pgast.TupleVar): val = tuple_var_as_json_object(expr, path_id=path_id, env=env) elif isinstance(expr, (pgast.RowExpr, pgast.ImplicitRowExpr)): val = pgast.FuncCall( name=_get_json_func('build_array', env=env), args=expr.args, null_safe=True, ser_safe=True,) elif path_id.is_collection_path() and not expr.ser_safe: val = coll_as_json_object(expr, styperef=path_id.target, env=env) elif not nested: val = pgast.FuncCall( name=_get_json_func('to', env=env), args=[expr], null_safe=True, ser_safe=True) else: val = expr return val
def range_for_typeref(typeref: irast.TypeRef, path_id: irast.PathId, *, include_overlays: bool = True, include_descendants: bool = True, dml_source: Optional[irast.MutatingStmt] = None, common_parent: bool = False, ctx: context.CompilerContextLevel) -> pgast.PathRangeVar: if typeref.common_parent is not None and common_parent: rvar = range_for_material_objtype( typeref.common_parent, path_id, include_overlays=include_overlays, include_descendants=include_descendants, dml_source=dml_source, ctx=ctx, ) elif typeref.union: # Union object types are represented as a UNION of selects # from their children, which is, for most purposes, equivalent # to SELECTing from a parent table. set_ops = [] for child in typeref.union: c_rvar = range_for_typeref( child, path_id=path_id, include_overlays=include_overlays, include_descendants=not typeref.union_is_concrete, dml_source=dml_source, ctx=ctx, ) qry = pgast.SelectStmt(from_clause=[c_rvar], ) pathctx.put_path_value_rvar(qry, path_id, c_rvar, env=ctx.env) if path_id.is_objtype_path(): pathctx.put_path_source_rvar(qry, path_id, c_rvar, env=ctx.env) pathctx.put_path_bond(qry, path_id) set_ops.append(('union', qry)) rvar = range_from_queryset(set_ops, typeref.name_hint, ctx=ctx) else: rvar = range_for_material_objtype( typeref, path_id, include_overlays=include_overlays, include_descendants=include_descendants, dml_source=dml_source, ctx=ctx, ) rvar.query.path_id = path_id return rvar
def _find_rvar_in_intersection_by_typeref( path_id: irast.PathId, component_rvars: Sequence[pgast.PathRangeVar], ) -> pgast.PathRangeVar: assert component_rvars pid_rptr = path_id.rptr() if pid_rptr is not None: if pid_rptr.material_ptr is not None: pid_rptr = pid_rptr.material_ptr tref = pid_rptr.out_source else: tref = path_id.target for component_rvar in component_rvars: if (component_rvar.typeref is not None and irtyputils.type_contains(tref, component_rvar.typeref)): rel_rvar = component_rvar break else: raise AssertionError( f'no rvar in intersection matches path id {path_id}') return rel_rvar
def extend_path_id( path_id: irast.PathId, *, ptrcls: s_pointers.PointerLike, direction: s_pointers.PointerDirection = ( s_pointers.PointerDirection.Outbound), ns: AbstractSet[str] = frozenset(), ctx: context.ContextLevel, ) -> irast.PathId: """A wrapper over :meth:`ir.pathid.PathId.extend` that also ensures the cardinality of *ptrcls* is known at the end of compilation. """ ptrref = irtyputils.ptrref_from_ptrcls( schema=ctx.env.schema, ptrcls=ptrcls, direction=direction, cache=ctx.env.ptr_ref_cache, typeref_cache=ctx.env.type_ref_cache, ) stmtctx.ensure_ptrref_cardinality(ptrcls, ptrref, ctx=ctx) return path_id.extend(ptrref=ptrref, direction=direction, ns=ns, schema=ctx.env.schema)
def _find_rel_rvar( rel: pgast.Query, path_id: irast.PathId, src_path_id: irast.PathId, *, aspect: str, env: context.Environment, ) -> Tuple[str, Optional[pgast.PathRangeVar], Optional[pgast.BaseExpr]]: """Rummage around rel looking for an appropriate rvar for path_id. Somewhat unfortunately, some checks to find the actual path var (in a particular tuple case) need to occur in the middle of the rvar rel search, so we can also find the actual path var in passing. """ src_aspect = aspect 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(): src_aspect = 'source' else: src_aspect = aspect if src_path_id.is_tuple_path(): if (var := _find_in_output_tuple(rel, path_id, aspect, env=env)): return src_aspect, None, var rel_rvar = maybe_get_path_rvar( rel, src_path_id, aspect=src_aspect, env=env) if rel_rvar is None: _src_path_id_prefix = src_path_id.src_path() if _src_path_id_prefix is not None: rel_rvar = maybe_get_path_rvar( rel, _src_path_id_prefix, aspect=src_aspect, env=env) else: rel_rvar = maybe_get_path_rvar( rel, src_path_id, aspect=src_aspect, env=env) if (rel_rvar is None and src_aspect != 'source' and path_id != src_path_id): rel_rvar = maybe_get_path_rvar( rel, src_path_id, aspect='source', env=env)
def map_path_id(path_id: irast.PathId, path_id_map: Dict[irast.PathId, irast.PathId]) -> irast.PathId: for outer_id, inner_id in path_id_map.items(): new_path_id = path_id.replace_prefix(outer_id, inner_id) if new_path_id != path_id: path_id = new_path_id break return path_id
def get_tuple_indirection_path_id(tuple_path_id: irast.PathId, element_name: str, element_type: s_types.Type, *, ctx: context.ContextLevel) -> irast.PathId: return tuple_path_id.extend( ptrcls=irast.TupleIndirectionLink(element_name), direction=s_pointers.PointerDirection.Outbound, target=element_type, schema=ctx.env.schema)
def _find_in_output_tuple( rel: pgast.Query, path_id: irast.PathId, aspect: str, env: context.Environment) -> Optional[pgast.BaseExpr]: """Try indirecting a source tuple already present as an output. Normally tuple indirections are handled by process_set_as_tuple_indirection, but UNIONing an explicit tuple with a tuple coming from a base relation (like `(Foo.bar UNION (1,2)).0`) can lead to us looking for a tuple path in relations that only have the actual full tuple. (See test_edgeql_coalesce_tuple_{08,09}). We handle this by checking whether some prefix of the tuple path is present in the path_outputs. This is sufficient because the relevant cases are all caused by set ops, and the "fixup" done in set op cases ensures that the tuple will be already present. """ steps = [] src_path_id = path_id.src_path() ptrref = path_id.rptr() while ( src_path_id and src_path_id.is_tuple_path() and isinstance(ptrref, irast.TupleIndirectionPointerRef) ): steps.append((ptrref.shortname.name, src_path_id)) if ( (var := rel.path_namespace.get((src_path_id, aspect))) and not isinstance(var, pgast.TupleVarBase) ): for name, src in reversed(steps): var = astutils.tuple_getattr(var, src.target, name) put_path_var(rel, path_id, var, aspect=aspect, env=env) return var ptrref = src_path_id.rptr() src_path_id = src_path_id.src_path()
def map_path_id(path_id: irast.PathId, path_id_map: Dict[irast.PathId, irast.PathId]) -> irast.PathId: sorted_map = sorted(path_id_map.items(), key=lambda kv: len(kv[0]), reverse=True) for outer_id, inner_id in sorted_map: new_path_id = path_id.replace_prefix(outer_id, inner_id) if new_path_id != path_id: path_id = new_path_id break return path_id
def get_less_specific_aspect( path_id: irast.PathId, aspect: str, ) -> Optional[str]: if path_id.is_objtype_path(): mapping = OBJECT_ASPECT_SPECIFICITY_MAP else: mapping = PRIMITIVE_ASPECT_SPECIFICITY_MAP less_specific_aspect = mapping.get(PathAspect(aspect)) if less_specific_aspect is not None: return str(less_specific_aspect) else: return None
def get_type_indirection_path_id(path_id: irast.PathId, target_type: s_types.Type, *, optional: bool, ancestral: bool, cardinality: qltypes.Cardinality, ctx: context.ContextLevel) -> irast.PathId: return path_id.extend(ptrcls=irast.TypeIndirectionLink( path_id.target, target_type, optional=optional, ancestral=ancestral, cardinality=cardinality), direction=s_pointers.PointerDirection.Outbound, target=target_type, schema=ctx.env.schema)
def process_view( *, stype: s_objtypes.ObjectType, path_id: irast.PathId, 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: Optional[pctx.ParserContext], ctx: context.ContextLevel, ) -> s_objtypes.ObjectType: cache_key = (stype, is_insert, is_update, is_delete, tuple(elements)) view_scls = ctx.shape_type_cache.get(cache_key) if view_scls is not None: return view_scls with ctx.newscope(fenced=True) as scopectx: scopectx.path_scope.is_temporary = True view_path_id_ns = None new_path_id = path_id if ctx.expr_exposed or is_insert or is_update: view_path_id_ns = ctx.aliases.get('tmpns') scopectx.path_id_namespace |= {view_path_id_ns} scopectx.path_scope.add_namespaces({view_path_id_ns}) new_path_id = path_id.merge_namespace({view_path_id_ns}) scopectx.path_scope.attach_path(new_path_id, context=parser_context) if ctx.path_log is not None: ctx.path_log.append(path_id) view_scls = _process_view( stype=stype, path_id=new_path_id, elements=elements, view_rptr=view_rptr, view_name=view_name, is_insert=is_insert, is_update=is_update, is_delete=is_delete, path_id_namespace=view_path_id_ns, parser_context=parser_context, ctx=scopectx, ) ctx.shape_type_cache[cache_key] = view_scls return view_scls
def new_external_rvar( *, rel_name: Tuple[str, ...], path_id: irast.PathId, outputs: Mapping[Tuple[irast.PathId, Tuple[str, ...]], str], ) -> pgast.RelRangeVar: """Construct a ``RangeVar`` instance given a relation name and a path id. Given an optionally-qualified relation name *rel_name* and a *path_id*, return a ``RangeVar`` instance over the specified relation that is then assumed to represent the *path_id* binding. This is useful in situations where it is necessary to "prime" the compiler with a list of external relations that exist in a larger SQL expression that _this_ expression is being embedded into. The *outputs* mapping optionally specifies a set of outputs in the resulting range var as a ``(path_id, tuple-of-aspects): attribute name`` mapping. """ if len(rel_name) == 1: table_name = rel_name[0] schema_name = None elif len(rel_name) == 2: schema_name, table_name = rel_name else: raise AssertionError(f'unexpected rvar name: {rel_name}') rel = pgast.Relation( name=table_name, schemaname=schema_name, path_id=path_id, ) alias = pgast.Alias(aliasname=table_name) if not path_id.is_ptr_path(): rvar = pgast.RelRangeVar( relation=rel, typeref=path_id.target, alias=alias) else: rvar = pgast.RelRangeVar( relation=rel, alias=alias) for (output_pid, output_aspects), colname in outputs.items(): var = pgast.ColumnRef(name=[colname]) for aspect in output_aspects: rel.path_outputs[output_pid, aspect] = var return rvar
def range_for_typeref(typeref: irast.TypeRef, path_id: irast.PathId, *, include_overlays: bool = True, common_parent: bool = False, env: context.Environment) -> pgast.BaseRangeVar: from . import pathctx # XXX: fix cycle if not typeref.children: rvar = range_for_material_objtype(typeref, path_id, include_overlays=include_overlays, env=env) elif common_parent: rvar = range_for_material_objtype(typeref.common_parent, path_id, include_overlays=include_overlays, env=env) else: # Union object types are represented as a UNION of selects # from their children, which is, for most purposes, equivalent # to SELECTing from a parent table. set_ops = [] for child in typeref.children: c_rvar = range_for_typeref(child, path_id=path_id, include_overlays=include_overlays, env=env) qry = pgast.SelectStmt(from_clause=[c_rvar], ) pathctx.put_path_value_rvar(qry, path_id, c_rvar, env=env) if path_id.is_objtype_path(): pathctx.put_path_source_rvar(qry, path_id, c_rvar, env=env) pathctx.put_path_bond(qry, path_id) set_ops.append(('union', qry)) rvar = range_from_queryset(set_ops, typeref.name_hint, env=env) rvar.query.path_id = path_id return rvar
def extend_path_id(path_id: irast.PathId, *, ptrcls, direction=None, target=None, ns=None, ctx: context.ContextLevel) -> irast.PathId: result = path_id.extend(ptrcls=ptrcls, direction=direction, target=target, ns=ns, schema=ctx.env.schema) ptrref = result.rptr() stmtctx.ensure_ptrref_cardinality(ptrcls, ptrref, ctx=ctx) return result
def get_tuple_indirection_path_id( tuple_path_id: irast.PathId, element_name: str, element_type: s_types.Type, *, ctx: context.ContextLevel) -> irast.PathId: ptrcls = irast.TupleIndirectionLink( irtyputils.ir_typeref_to_type(ctx.env.schema, tuple_path_id.target), element_type, element_name=element_name, ) ptrref = irtyputils.ptrref_from_ptrcls( schema=ctx.env.schema, ptrcls=ptrcls, # FIXME: caching disabled here since it breaks tests # cache=ctx.env.ptr_ref_cache, # typeref_cache=ctx.env.type_ref_cache, ) return tuple_path_id.extend(schema=ctx.env.schema, ptrref=ptrref)
def include_rvar(stmt: pgast.SelectStmt, rvar: pgast.PathRangeVar, path_id: irast.PathId, *, overwrite_path_rvar: bool = False, pull_namespace: bool = True, aspects: Optional[Iterable[str]] = None, ctx: context.CompilerContextLevel) -> pgast.PathRangeVar: """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 _find_rvar_in_intersection( path_id: irast.PathId, component_rvars: Sequence[pgast.PathRangeVar], ) -> pgast.PathRangeVar: assert component_rvars pid_rptr = path_id.rptr() if pid_rptr is not None: if pid_rptr.material_ptr is not None: pid_rptr = pid_rptr.material_ptr tref = pid_rptr.out_source else: tref = path_id.target for component_rvar in component_rvars: assert component_rvar.typeref is not None if irtyputils.type_contains(tref, component_rvar.typeref): rel_rvar = component_rvar break else: rel_rvar = component_rvars[0] return rel_rvar
def get_path_var(rel: pgast.Query, path_id: irast.PathId, *, aspect: str, env: context.Environment) -> pgast.BaseExpr: """Return a value expression 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] ptrref = path_id.rptr() is_type_indirection = path_id.is_type_indirection_path() if ptrref is not None and not is_type_indirection: ptr_info = pg_types.get_ptrref_storage_info(ptrref, 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() src_rptr = src_path_id.rptr() if (irtyputils.is_id_ptrref(ptrref) and (src_rptr is None or not irtyputils.is_inbound_ptrref(src_rptr))): # When there is a reference to the id property of # an object which is linked to by a link stored # inline, we want to route the reference to the # inline attribute. For example, # Foo.__type__.id gets resolved to the Foo.__type__ # column. This can only be done if Foo is visible # in scope, and Foo.__type__ is not a computable. pid = src_path_id while pid.is_type_indirection_path(): # Skip type indirection step(s). src_pid = pid.src_path() if src_pid is not None: src_rptr = src_pid.rptr() pid = src_pid else: break src_src_is_visible = env.ptrref_source_visibility.get(src_rptr) if (src_rptr is not None and not irtyputils.is_computable_ptrref(src_rptr) and src_src_is_visible): src_ptr_info = pg_types.get_ptrref_storage_info( src_rptr, resolve_type=False, link_bias=False) if src_ptr_info.table_type == 'ObjectType': src_path_id = src_path_id.src_path() ptr_info = src_ptr_info else: ptr_info = None src_path_id = None ptr_dir = None var: typing.Optional[pgast.BaseExpr] 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 = astutils.strip_output_var(first, optional=optional, nullable=optional or nullable) put_path_var(rel, path_id, var, aspect=aspect, env=env) return var if ptrref is None: if len(path_id) == 1: # This is an scalar set derived from an expression. src_path_id = path_id elif ptrref.parent_ptr is not None: 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 (is_type_indirection or (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(): src_aspect = 'source' else: src_aspect = aspect if src_path_id.is_tuple_path(): 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 (rel_rvar is None and 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 ptrref 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 = astutils.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_if_not_exists(rel, element.path_id, element.val, aspect=aspect, env=env) return var
def new_primitive_rvar( ir_set: irast.Set, *, path_id: irast.PathId, ctx: context.CompilerContextLevel, ) -> pgast.PathRangeVar: if not ir_set.path_id.is_objtype_path(): raise ValueError('cannot create root rvar for non-object path') typeref = ir_set.typeref dml_source = irutils.get_nearest_dml_stmt(ir_set) set_rvar = range_for_typeref(typeref, path_id, dml_source=dml_source, ctx=ctx) pathctx.put_rvar_path_bond(set_rvar, path_id) if ir_set.rptr is not None: ptr_ref_map: Dict[uuid.UUID, irast.BasePointerRef] = {} p: irast.BasePointerRef rptrref = ir_set.rptr.ptrref if isinstance(rptrref, irast.TypeIntersectionPointerRef): if rptrref.rptr_specialization: for p in rptrref.rptr_specialization: ptr_ref_map[p.dir_target.id] = p src_set = ir_set.rptr.source if src_set.rptr is not None: src_rptrref = src_set.rptr.ptrref if src_rptrref.union_components: for p in src_rptrref.union_components: ptr_ref_map[p.dir_target.id] = p else: ptr_ref_map[src_rptrref.dir_target.id] = src_rptrref rptrref = src_rptrref else: ptr_ref_map[rptrref.dir_target.id] = rptrref else: if rptrref.union_components: for p in rptrref.union_components: ptr_ref_map[p.dir_target.id] = p else: ptr_ref_map[rptrref.dir_target.id] = rptrref if (set_rvar.typeref is not None and (narrow_rptrref := ptr_ref_map.get(set_rvar.typeref.id))): rptrref = narrow_rptrref ptr_info = pg_types.get_ptrref_storage_info(rptrref, resolve_type=False, link_bias=False) if ptr_info.table_type == 'ObjectType' and rptrref.is_inbound: # Inline link prefix_path_id = path_id.src_path() assert prefix_path_id is not None, 'expected a path' rref = pgast.ColumnRef(name=[ptr_info.column_name], nullable=not rptrref.required) pathctx.put_rvar_path_bond(set_rvar, prefix_path_id) pathctx.put_rvar_path_output(set_rvar, prefix_path_id, aspect='identity', var=rref, env=ctx.env) if astutils.is_set_op_query(set_rvar.query): assert isinstance(set_rvar.query, pgast.SelectStmt) def _pull_col(comp_qry: pgast.Query) -> None: rvar = pathctx.get_path_rvar(comp_qry, path_id, aspect='source', env=ctx.env) typeref = rvar.typeref assert typeref is not None comp_ptrref = ptr_ref_map[typeref.id] comp_pi = pg_types.get_ptrref_storage_info( comp_ptrref, resolve_type=False, link_bias=False) comp_qry.target_list.append( pgast.ResTarget( val=pgast.ColumnRef(name=[comp_pi.column_name]), name=ptr_info.column_name, )) astutils.for_each_query_in_set( set_rvar.query, _pull_col, ) elif isinstance(set_rvar, pgast.RangeSubselect): rvar_path_var = pathctx.maybe_get_path_rvar( set_rvar.query, path_id=path_id, aspect='identity', env=ctx.env, ) if isinstance(rvar_path_var, pgast.IntersectionRangeVar): for comp_rvar in rvar_path_var.component_rvars: if comp_rvar.typeref is None: continue comp_ptrref = ptr_ref_map.get(comp_rvar.typeref.id) if comp_ptrref is None: continue comp_pi = pg_types.get_ptrref_storage_info( comp_ptrref, resolve_type=False) set_rvar.query.target_list.append( pgast.ResTarget( val=pgast.ColumnRef(name=[ comp_rvar.alias.aliasname, comp_pi.column_name, ]), name=ptr_info.column_name, ))
def path_is_banned( path_id: irast.PathId, *, ctx: context.ContextLevel) -> bool: s_path_id = path_id.strip_weak_namespaces() return s_path_id in ctx.banned_paths and ctx.path_scope.is_visible(path_id)
def ban_path( path_id: irast.PathId, *, ctx: context.ContextLevel) -> None: ctx.banned_paths.add(path_id.strip_weak_namespaces())
def range_for_material_objtype( typeref: irast.TypeRef, path_id: irast.PathId, *, include_overlays: bool = True, include_descendants: bool = True, dml_source: Optional[irast.MutatingStmt] = None, ctx: context.CompilerContextLevel) -> pgast.PathRangeVar: env = ctx.env if typeref.material_type is not None: typeref = typeref.material_type table_schema_name, table_name = common.get_objtype_backend_name( typeref.id, typeref.module_id, catenate=False) if typeref.name_hint.module in {'cfg', 'sys'}: # Redirect all queries to schema tables to edgedbss table_schema_name = 'edgedbss' relation = pgast.Relation( schemaname=table_schema_name, name=table_name, path_id=path_id, ) rvar: pgast.PathRangeVar = pgast.RelRangeVar( relation=relation, typeref=typeref, include_inherited=include_descendants, alias=pgast.Alias(aliasname=env.aliases.get(typeref.name_hint.name))) overlays = get_type_rel_overlays(typeref, dml_source=dml_source, ctx=ctx) if overlays and include_overlays: set_ops = [] qry = pgast.SelectStmt() qry.from_clause.append(rvar) pathctx.put_path_value_rvar(qry, path_id, rvar, env=env) if path_id.is_objtype_path(): pathctx.put_path_source_rvar(qry, path_id, rvar, env=env) pathctx.put_path_bond(qry, path_id) set_ops.append(('union', qry)) for op, cte, cte_path_id in overlays: rvar = pgast.RelRangeVar( relation=cte, typeref=typeref, alias=pgast.Alias(aliasname=env.aliases.get(hint=cte.name))) qry = pgast.SelectStmt(from_clause=[rvar], ) pathctx.put_path_value_rvar(qry, cte_path_id, rvar, env=env) if path_id.is_objtype_path(): pathctx.put_path_source_rvar(qry, cte_path_id, rvar, env=env) pathctx.put_path_bond(qry, cte_path_id) qry.view_path_id_map[path_id] = cte_path_id qry_rvar = pgast.RangeSubselect( subquery=qry, alias=pgast.Alias(aliasname=env.aliases.get(hint=cte.name))) qry2 = pgast.SelectStmt(from_clause=[qry_rvar]) pathctx.put_path_value_rvar(qry2, path_id, qry_rvar, env=env) if path_id.is_objtype_path(): pathctx.put_path_source_rvar(qry2, path_id, qry_rvar, env=env) pathctx.put_path_bond(qry2, path_id) if op == 'replace': op = 'union' set_ops = [] set_ops.append((op, qry2)) rvar = range_from_queryset(set_ops, typeref.name_hint, ctx=ctx) return rvar
def _normalize_view_ptr_expr( shape_el: qlast.ShapeElement, view_scls: s_objtypes.ObjectType, *, path_id: irast.PathId, path_id_namespace: Optional[irast.WeakNamespace]=None, is_insert: bool=False, is_update: bool=False, from_default: bool=False, view_rptr: Optional[context.ViewRPtr]=None, ctx: context.ContextLevel) -> s_pointers.Pointer: steps = shape_el.expr.steps is_linkprop = False is_polymorphic = False is_mutation = is_insert or is_update # Pointers may be qualified by the explicit source # class, which is equivalent to Expr[IS Type]. plen = len(steps) ptrsource: s_sources.Source = view_scls qlexpr: Optional[qlast.Expr] = None target_typexpr = None source: qlast.Base base_ptrcls_is_alias = False if plen >= 2 and isinstance(steps[-1], qlast.TypeIntersection): # Target type intersection: foo: Type target_typexpr = steps[-1].type plen -= 1 steps = steps[:-1] if plen == 1: # regular shape lexpr = steps[0] assert isinstance(lexpr, qlast.Ptr) is_linkprop = lexpr.type == 'property' if is_linkprop: if view_rptr is None or view_rptr.ptrcls is None: raise errors.QueryError( 'invalid reference to link property ' 'in top level shape', context=lexpr.context) assert isinstance(view_rptr.ptrcls, s_links.Link) ptrsource = view_rptr.ptrcls source = qlast.Source() elif plen == 2 and isinstance(steps[0], qlast.TypeIntersection): # Source type intersection: [IS Type].foo source = qlast.Path(steps=[ qlast.Source(), steps[0], ]) lexpr = steps[1] ptype = steps[0].type if not isinstance(ptype, qlast.TypeName): raise errors.QueryError( 'complex type expressions are not supported here', context=ptype.context, ) source_spec = schemactx.get_schema_type(ptype.maintype, ctx=ctx) if not isinstance(source_spec, s_objtypes.ObjectType): raise errors.QueryError( f'expected object type, got ' f'{source_spec.get_verbosename(ctx.env.schema)}', context=ptype.context, ) ptrsource = source_spec is_polymorphic = True else: # pragma: no cover raise RuntimeError( f'unexpected path length in view shape: {len(steps)}') assert isinstance(lexpr, qlast.Ptr) ptrname = lexpr.ptr.name compexpr: Optional[qlast.Expr] = shape_el.compexpr if compexpr is None and is_insert and shape_el.elements: # Short shape form in INSERT, e.g # INSERT Foo { bar: Spam { name := 'name' }} # is prohibited. raise errors.EdgeQLSyntaxError( "unexpected ':'", context=steps[-1].context) ptrcls: Optional[s_pointers.Pointer] if compexpr is None: ptrcls = setgen.resolve_ptr( ptrsource, ptrname, track_ref=lexpr, ctx=ctx) if is_polymorphic: ptrcls = schemactx.derive_ptr( ptrcls, view_scls, is_insert=is_insert, is_update=is_update, ctx=ctx) base_ptrcls = ptrcls.get_bases(ctx.env.schema).first(ctx.env.schema) base_ptr_is_computable = base_ptrcls in ctx.source_map ptr_name = sn.QualName( module='__', name=ptrcls.get_shortname(ctx.env.schema).name, ) base_cardinality = _get_base_ptr_cardinality(base_ptrcls, ctx=ctx) base_is_singleton = False if base_cardinality is not None and base_cardinality.is_known(): base_is_singleton = base_cardinality.is_single() if ( shape_el.where or shape_el.orderby or shape_el.offset or shape_el.limit or base_ptr_is_computable or is_polymorphic or target_typexpr is not None or (ctx.implicit_limit and not base_is_singleton) ): if target_typexpr is None: qlexpr = qlast.Path(steps=[source, lexpr]) else: qlexpr = qlast.Path(steps=[ source, lexpr, qlast.TypeIntersection(type=target_typexpr), ]) qlexpr = astutils.ensure_qlstmt(qlexpr) assert isinstance(qlexpr, qlast.SelectQuery) qlexpr.where = shape_el.where qlexpr.orderby = shape_el.orderby if shape_el.offset or shape_el.limit: qlexpr = qlast.SelectQuery(result=qlexpr, implicit=True) qlexpr.offset = shape_el.offset qlexpr.limit = shape_el.limit if ( (ctx.expr_exposed or ctx.stmt is ctx.toplevel_stmt) and not qlexpr.limit and ctx.implicit_limit and not base_is_singleton ): qlexpr.limit = qlast.IntegerConstant( value=str(ctx.implicit_limit), ) if target_typexpr is not None: assert isinstance(target_typexpr, qlast.TypeName) intersector_type = schemactx.get_schema_type( target_typexpr.maintype, ctx=ctx) int_result = schemactx.apply_intersection( ptrcls.get_target(ctx.env.schema), # type: ignore intersector_type, ctx=ctx, ) ptr_target = int_result.stype else: _ptr_target = ptrcls.get_target(ctx.env.schema) assert _ptr_target ptr_target = _ptr_target ptr_cardinality = base_cardinality if ptr_cardinality is None or not ptr_cardinality.is_known(): # We do not know the parent's pointer cardinality yet. ctx.env.pointer_derivation_map[base_ptrcls].append(ptrcls) ctx.env.pointer_specified_info[ptrcls] = ( shape_el.cardinality, shape_el.required, shape_el.context) implicit_tid = has_implicit_type_computables( ptr_target, is_mutation=is_mutation, ctx=ctx, ) if shape_el.elements or implicit_tid: 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 = pathctx.extend_path_id( path_id, ptrcls=base_ptrcls, ns=ctx.path_id_namespace, ctx=ctx) ctx.path_scope.attach_path(sub_path_id, context=shape_el.context) if not isinstance(ptr_target, s_objtypes.ObjectType): raise errors.QueryError( f'shapes cannot be applied to ' f'{ptr_target.get_verbosename(ctx.env.schema)}', context=shape_el.context, ) 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.QueryError( 'only references to link properties are allowed ' 'in nested UPDATE shapes', context=subel.context) ptr_target = _process_view( stype=ptr_target, path_id=sub_path_id, path_id_namespace=path_id_namespace, view_rptr=sub_view_rptr, elements=shape_el.elements, is_update=True, parser_context=shape_el.context, ctx=ctx) else: ptr_target = _process_view( stype=ptr_target, path_id=sub_path_id, path_id_namespace=path_id_namespace, view_rptr=sub_view_rptr, elements=shape_el.elements, parser_context=shape_el.context, ctx=ctx) else: base_ptrcls = ptrcls = None if (is_mutation and ptrname not in ctx.special_computables_in_mutation_shape): # If this is a mutation, the pointer must exist. ptrcls = setgen.resolve_ptr( ptrsource, ptrname, track_ref=lexpr, ctx=ctx) base_ptrcls = ptrcls.get_bases( ctx.env.schema).first(ctx.env.schema) ptr_name = sn.QualName( module='__', name=ptrcls.get_shortname(ctx.env.schema).name, ) else: ptr_name = sn.QualName( module='__', name=ptrname, ) try: ptrcls = setgen.resolve_ptr( ptrsource, ptrname, track_ref=False, ctx=ctx, ) base_ptrcls = ptrcls.get_bases( ctx.env.schema).first(ctx.env.schema) except errors.InvalidReferenceError: # This is a NEW computable pointer, it's fine. pass qlexpr = astutils.ensure_qlstmt(compexpr) if ((ctx.expr_exposed or ctx.stmt is ctx.toplevel_stmt) and ctx.implicit_limit and isinstance(qlexpr, qlast.OffsetLimitMixin) and not qlexpr.limit): qlexpr.limit = qlast.IntegerConstant(value=str(ctx.implicit_limit)) 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.defining_view = view_scls shape_expr_ctx.path_scope.unnest_fence = True shape_expr_ctx.partial_path_prefix = setgen.class_set( view_scls.get_bases(ctx.env.schema).first(ctx.env.schema), path_id=path_id, ctx=shape_expr_ctx) prefix_rptrref = path_id.rptr() if prefix_rptrref is not None: # Source path seems to contain multiple steps, # so set up a rptr for abbreviated link property # paths. src_path_id = path_id.src_path() assert src_path_id is not None ctx.env.schema, src_t = irtyputils.ir_typeref_to_type( shape_expr_ctx.env.schema, src_path_id.target, ) prefix_rptr = irast.Pointer( source=setgen.class_set( src_t, path_id=src_path_id, ctx=shape_expr_ctx, ), target=shape_expr_ctx.partial_path_prefix, ptrref=prefix_rptrref, direction=s_pointers.PointerDirection.Outbound, ) shape_expr_ctx.partial_path_prefix.rptr = prefix_rptr if is_mutation and ptrcls is not None: shape_expr_ctx.expr_exposed = True shape_expr_ctx.empty_result_type_hint = \ ptrcls.get_target(ctx.env.schema) shape_expr_ctx.stmt_metadata[qlexpr] = context.StatementMetadata( iterator_target=True, ) irexpr = dispatch.compile(qlexpr, ctx=shape_expr_ctx) if ( shape_el.operation.op is qlast.ShapeOp.APPEND or shape_el.operation.op is qlast.ShapeOp.SUBTRACT ): if not is_update: op = ( '+=' if shape_el.operation.op is qlast.ShapeOp.APPEND else '-=' ) raise errors.EdgeQLSyntaxError( f"unexpected '{op}'", context=shape_el.operation.context, ) irexpr.context = compexpr.context if base_ptrcls is None: base_ptrcls = shape_expr_ctx.view_rptr.base_ptrcls base_ptrcls_is_alias = shape_expr_ctx.view_rptr.ptrcls_is_alias if ptrcls is not None: ctx.env.schema = ptrcls.set_field_value( ctx.env.schema, 'owned', True) ptr_cardinality = None ptr_target = inference.infer_type(irexpr, ctx.env) if ( isinstance(ptr_target, s_types.Collection) and not ctx.env.orig_schema.get_by_id(ptr_target.id, default=None) ): # Record references to implicitly defined collection types, # so that the alias delta machinery can pick them up. ctx.env.created_schema_objects.add(ptr_target) anytype = ptr_target.find_any(ctx.env.schema) if anytype is not None: raise errors.QueryError( 'expression returns value of indeterminate type', context=ctx.env.type_origins.get(anytype), ) # Validate that the insert/update expression is # of the correct class. if is_mutation and ptrcls is not None: base_target = ptrcls.get_target(ctx.env.schema) assert base_target is not None if ptr_target.assignment_castable_to( base_target, schema=ctx.env.schema): # Force assignment casts if the target type is not a # subclass of the base type and the cast is not to an # object type. if not ( base_target.is_object_type() or s_types.is_type_compatible( base_target, ptr_target, schema=ctx.env.schema ) ): qlexpr = astutils.ensure_qlstmt(qlast.TypeCast( type=typegen.type_to_ql_typeref(base_target, ctx=ctx), expr=compexpr, )) ptr_target = base_target else: expected = [ repr(str(base_target.get_displayname(ctx.env.schema))) ] ercls: Type[errors.EdgeDBError] if ptrcls.is_property(ctx.env.schema): ercls = errors.InvalidPropertyTargetError else: ercls = errors.InvalidLinkTargetError ptr_vn = ptrcls.get_verbosename(ctx.env.schema, with_parent=True) raise ercls( f'invalid target for {ptr_vn}: ' f'{str(ptr_target.get_displayname(ctx.env.schema))!r} ' f'(expecting {" or ".join(expected)})' ) if qlexpr is not None or ptrcls is None: src_scls: s_sources.Source if is_linkprop: # Proper checking was done when is_linkprop is defined. assert view_rptr is not None assert isinstance(view_rptr.ptrcls, s_links.Link) src_scls = view_rptr.ptrcls else: src_scls = view_scls if ptr_target.is_object_type(): base = ctx.env.get_track_schema_object( sn.QualName('std', 'link'), expr=None) else: base = ctx.env.get_track_schema_object( sn.QualName('std', 'property'), expr=None) if base_ptrcls is not None: derive_from = base_ptrcls else: derive_from = base derived_name = schemactx.derive_view_name( base_ptrcls, derived_name_base=ptr_name, derived_name_quals=[str(src_scls.get_name(ctx.env.schema))], ctx=ctx, ) existing = ctx.env.schema.get( derived_name, default=None, type=s_pointers.Pointer) if existing is not None: existing_target = existing.get_target(ctx.env.schema) assert existing_target is not None if ctx.recompiling_schema_alias: ptr_cardinality = existing.get_cardinality(ctx.env.schema) if ptr_target == existing_target: ptrcls = existing elif ptr_target.implicitly_castable_to( existing_target, ctx.env.schema): ctx.env.schema = existing.set_target( ctx.env.schema, ptr_target) ptrcls = existing else: vnp = existing.get_verbosename( ctx.env.schema, with_parent=True) t1_vn = existing_target.get_verbosename(ctx.env.schema) t2_vn = ptr_target.get_verbosename(ctx.env.schema) if compexpr is not None: source_context = compexpr.context else: source_context = shape_el.expr.steps[-1].context raise errors.SchemaError( f'cannot redefine {vnp} as {t2_vn}', details=f'{vnp} is defined as {t1_vn}', context=source_context, ) else: ptrcls = schemactx.derive_ptr( derive_from, src_scls, ptr_target, is_insert=is_insert, is_update=is_update, derived_name=derived_name, ctx=ctx) elif ptrcls.get_target(ctx.env.schema) != ptr_target: ctx.env.schema = ptrcls.set_target(ctx.env.schema, ptr_target) assert ptrcls is not None if qlexpr is None: # This is not a computable, just a pointer # to a nested shape. Have it reuse the original # pointer name so that in `Foo.ptr.name` and # `Foo { ptr: {name}}` are the same path. path_id_name = base_ptrcls.get_name(ctx.env.schema) ctx.env.schema = ptrcls.set_field_value( ctx.env.schema, 'path_id_name', path_id_name ) if qlexpr is not None: ctx.source_map[ptrcls] = irast.ComputableInfo( qlexpr=qlexpr, context=ctx, path_id=path_id, path_id_ns=path_id_namespace, shape_op=shape_el.operation.op, ) if compexpr is not None or is_polymorphic: ctx.env.schema = ptrcls.set_field_value( ctx.env.schema, 'computable', True, ) ctx.env.schema = ptrcls.set_field_value( ctx.env.schema, 'owned', True, ) if ptr_cardinality is not None: ctx.env.schema = ptrcls.set_field_value( ctx.env.schema, 'cardinality', ptr_cardinality) else: if qlexpr is None and ptrcls is not base_ptrcls: ctx.env.pointer_derivation_map[base_ptrcls].append(ptrcls) base_cardinality = None base_required = False if base_ptrcls is not None and not base_ptrcls_is_alias: base_cardinality = _get_base_ptr_cardinality(base_ptrcls, ctx=ctx) base_required = base_ptrcls.get_required(ctx.env.schema) if base_cardinality is None or not base_cardinality.is_known(): specified_cardinality = shape_el.cardinality specified_required = shape_el.required else: specified_cardinality = base_cardinality specified_required = base_required if (shape_el.cardinality is not None and base_ptrcls is not None and shape_el.cardinality != base_cardinality): base_src = base_ptrcls.get_source(ctx.env.schema) assert base_src is not None base_src_name = base_src.get_verbosename(ctx.env.schema) raise errors.SchemaError( f'cannot redefine the cardinality of ' f'{ptrcls.get_verbosename(ctx.env.schema)}: ' f'it is defined as {base_cardinality.as_ptr_qual()!r} ' f'in the base {base_src_name}', context=compexpr and compexpr.context, ) # The required flag may be inherited from the base specified_required = shape_el.required or base_required ctx.env.pointer_specified_info[ptrcls] = ( specified_cardinality, specified_required, shape_el.context) ctx.env.schema = ptrcls.set_field_value( ctx.env.schema, 'cardinality', qltypes.SchemaCardinality.Unknown) if ( ptrcls.is_protected_pointer(ctx.env.schema) and qlexpr is not None and not from_default and not ctx.env.options.allow_writing_protected_pointers ): ptrcls_sn = ptrcls.get_shortname(ctx.env.schema) if is_polymorphic: msg = (f'cannot access {ptrcls_sn.name} on a polymorphic ' f'shape element') else: msg = f'cannot assign to {ptrcls_sn.name}' raise errors.QueryError(msg, context=shape_el.context) if is_update and ptrcls.get_readonly(ctx.env.schema): raise errors.QueryError( f'cannot update {ptrcls.get_verbosename(ctx.env.schema)}: ' f'it is declared as read-only', context=compexpr and compexpr.context, ) return ptrcls
def _get_path_output(rel: pgast.BaseRelation, path_id: irast.PathId, *, aspect: str, allow_nullable: bool = True, ptr_info: typing.Optional[ pg_types.PointerStorageInfo] = None, env: context.Environment) -> pgast.OutputVar: result = rel.path_outputs.get((path_id, aspect)) if result is not None: return result ref: pgast.BaseExpr alias = None rptr = path_id.rptr() if rptr is not None and irtyputils.is_id_ptrref(rptr): # A value reference to Object.id is the same as a value # reference to the Object itself. src_path_id = path_id.src_path() id_output = rel.path_outputs.get((src_path_id, 'value')) if id_output is not None: _put_path_output_var(rel, path_id, aspect, id_output, env=env) return id_output if is_terminal_relation(rel): return _get_rel_path_output(rel, path_id, aspect=aspect, ptr_info=ptr_info, env=env) assert isinstance(rel, pgast.Query) if is_values_relation(rel): # The VALUES() construct seems to always expose its # value as "column1". alias = 'column1' ref = pgast.ColumnRef(name=[alias]) else: ref = get_path_var(rel, path_id, aspect=aspect, env=env) other_output = find_path_output(rel, path_id, ref, env=env) if other_output is not None: _put_path_output_var(rel, path_id, aspect, other_output, env=env) return other_output if isinstance(ref, pgast.TupleVarBase): elements = [] for el in ref.elements: el_path_id = reverse_map_path_id(el.path_id, rel.view_path_id_map) try: # Similarly to get_path_var(), check for outer path_id # first for tuple serialized var disambiguation. element = _get_path_output(rel, el_path_id, aspect=aspect, allow_nullable=False, env=env) except LookupError: element = get_path_output(rel, el_path_id, aspect=aspect, allow_nullable=False, env=env) elements.append( pgast.TupleElementBase(path_id=el_path_id, name=element)) result = pgast.TupleVarBase(elements=elements, named=ref.named) else: if astutils.is_set_op_query(rel): assert isinstance(ref, pgast.OutputVar) result = astutils.strip_output_var(ref) else: assert isinstance(rel, pgast.ReturningQuery), \ "expected ReturningQuery" if alias is None: alias = get_path_output_alias(path_id, aspect, env=env) restarget = pgast.ResTarget(name=alias, val=ref, ser_safe=getattr( ref, 'ser_safe', False)) rel.target_list.append(restarget) nullable = is_nullable(ref, env=env) optional = None if isinstance(ref, pgast.ColumnRef): optional = ref.optional if nullable and not allow_nullable: var = get_path_var(rel, path_id, aspect=aspect, env=env) rel.where_clause = astutils.extend_binop( rel.where_clause, pgast.NullTest(arg=var, negated=True)) nullable = False result = pgast.ColumnRef(name=[alias], nullable=nullable, optional=optional) _put_path_output_var(rel, path_id, aspect, result, env=env) if (path_id.is_objtype_path() and not isinstance(result, pgast.TupleVarBase)): equiv_aspect = None if aspect == 'identity': equiv_aspect = 'value' elif aspect == 'value': equiv_aspect = 'identity' if (equiv_aspect is not None and (path_id, equiv_aspect) not in rel.path_outputs): _put_path_output_var(rel, path_id, equiv_aspect, result, env=env) return result