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 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 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 _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 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 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 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 _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
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())): return _get_rel_object_id_output(rel, path_id, aspect=aspect, env=env) 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 ptrref = 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 ptrref is not None: target = ptrref.out_target else: target = path_id.target pg_type = pg_types.pg_type_from_ir_typeref(target) if ptr_info is not None: name = env.aliases.get(ptr_info.column_name) else: name = env.aliases.get('v') val = pgast.TypeCast(arg=pgast.NullConstant(), type_name=pgast.TypeName(name=pg_type, )) rel.target_list.append(pgast.ResTarget(name=name, val=val)) result = pgast.ColumnRef(name=[name], nullable=True) else: if ptrref is None: raise ValueError( f'could not resolve trailing pointer class for {path_id}') if ptr_info is None: ptr_info = pg_types.get_ptrref_storage_info(ptrref, resolve_type=False, link_bias=False) result = pgast.ColumnRef(name=[ptr_info.column_name], nullable=not ptrref.required) _put_path_output_var(rel, path_id, aspect, result, env=env) return result
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_intersection = path_id.is_type_intersection_path() src_path_id: Optional[irast.PathId] = None if ptrref is not None and not is_type_intersection: 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() assert src_path_id is not None 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_intersection_path(): # Skip type intersection step(s). src_pid = pid.src_path() if src_pid is not None: src_rptr = src_pid.rptr() pid = src_pid else: break if (src_rptr is not None and not irtyputils.is_computable_ptrref(src_rptr) and env.ptrref_source_visibility.get(src_rptr)): 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 ptr_dir = None var: Optional[pgast.BaseExpr] if astutils.is_set_op_query(rel): # We disable the find_path_output optimizaiton when doing # UNIONs to avoid situations where they have different numbers # of columns. cb = functools.partial(get_path_output_or_null, env=env, disable_output_fusion=True, path_id=path_id, aspect=aspect) outputs = astutils.for_each_query_in_set(rel, cb) first: Optional[pgast.OutputVar] = 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}') if first is None: raise AssertionError(f'union did not produce any outputs') # 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.source_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. _prefix_pid = path_id.src_path() assert _prefix_pid is not None src_path_id = _prefix_pid.src_path() elif (ptr_info is not None and ptr_info.table_type != 'ObjectType' and not is_inbound): # Ref is in the mapping rvar. src_path_id = path_id.ptr_path() elif is_type_intersection: src_path_id = path_id 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 assert src_path_id is not 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: _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) 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}') if isinstance(rel_rvar, pgast.IntersectionRangeVar): if ((path_id.is_objtype_path() and src_path_id == path_id) or (ptrref is not None and irtyputils.is_id_ptrref(ptrref))): rel_rvar = rel_rvar.component_rvars[-1] else: # Intersection rvars are basically JOINs of the relevant # parts of the type intersection, and so we need to make # sure we pick the correct component relation of that JOIN. rel_rvar = _find_rvar_in_intersection_by_typeref( path_id, rel_rvar.component_rvars, ) source_rel = rel_rvar.query if isinstance(ptrref, irast.PointerRef) and rel_rvar.typeref is not None: actual_ptrref = irtyputils.maybe_find_actual_ptrref( rel_rvar.typeref, ptrref) if actual_ptrref is not None: ptr_info = pg_types.get_ptrref_storage_info(actual_ptrref, resolve_type=False, link_bias=False) outvar = get_path_output(source_rel, 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 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] if astutils.is_set_op_query(rel): return _get_path_var_in_setop(rel, path_id, aspect=aspect, env=env) ptrref = path_id.rptr() is_type_intersection = path_id.is_type_intersection_path() src_path_id: Optional[irast.PathId] = None if ptrref is not None and not is_type_intersection: ptr_info = pg_types.get_ptrref_storage_info( ptrref, resolve_type=False, link_bias=False, allow_missing=True) 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() assert src_path_id is not None 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_intersection_path(): # Skip type intersection step(s). src_pid = pid.src_path() if src_pid is not None: src_rptr = src_pid.rptr() pid = src_pid else: break if (src_rptr is not None and not irtyputils.is_computable_ptrref(src_rptr) and env.ptrref_source_visibility.get(src_rptr)): src_ptr_info = pg_types.get_ptrref_storage_info( src_rptr, resolve_type=False, link_bias=False, allow_missing=True) if (src_ptr_info and src_ptr_info.table_type == 'ObjectType'): src_path_id = src_path_id.src_path() ptr_info = src_ptr_info else: ptr_info = None ptr_dir = None var: Optional[pgast.BaseExpr] 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.source_ptr is not None: if ptr_info and 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. _prefix_pid = path_id.src_path() assert _prefix_pid is not None src_path_id = _prefix_pid.src_path() elif is_type_intersection: src_path_id = path_id assert src_path_id is not None # Find which rvar will have path_id as an output src_aspect, rel_rvar, found_path_var = _find_rel_rvar( rel, path_id, src_path_id, aspect=aspect, env=env) if found_path_var: return found_path_var if rel_rvar is None: raise LookupError( f'there is no range var for ' f'{src_path_id} {src_aspect} in {rel}') if isinstance(rel_rvar, pgast.IntersectionRangeVar): if ( (path_id.is_objtype_path() and src_path_id == path_id) or (ptrref is not None and irtyputils.is_id_ptrref(ptrref)) ): rel_rvar = rel_rvar.component_rvars[-1] else: # Intersection rvars are basically JOINs of the relevant # parts of the type intersection, and so we need to make # sure we pick the correct component relation of that JOIN. rel_rvar = _find_rvar_in_intersection_by_typeref( path_id, rel_rvar.component_rvars, ) source_rel = rel_rvar.query if isinstance(ptrref, irast.PointerRef) and rel_rvar.typeref is not None: actual_ptrref = irtyputils.maybe_find_actual_ptrref( rel_rvar.typeref, ptrref) if actual_ptrref is not None: ptr_info = pg_types.get_ptrref_storage_info( actual_ptrref, resolve_type=False, link_bias=False) outvar = get_path_output( source_rel, 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