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): ptr_si: Optional[pg_types.PointerStorageInfo] 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 _compile_set_in_singleton_mode( node: irast.Set, *, ctx: context.CompilerContextLevel) -> pgast.BaseExpr: if isinstance(node, irast.EmptySet): return pgast.NullConstant() elif node.expr is not None: return dispatch.compile(node.expr, ctx=ctx) else: if node.rptr: ptrref = node.rptr.ptrref source = node.rptr.source if ptrref.source_ptr is None and source.rptr is not None: raise RuntimeError('unexpectedly long path in simple expr') ptr_stor_info = pg_types.get_ptrref_storage_info( ptrref, resolve_type=False) colref = pgast.ColumnRef(name=[ptr_stor_info.column_name]) elif irtyputils.is_scalar(node.typeref): colref = pgast.ColumnRef( name=[common.edgedb_name_to_pg_name(str(node.typeref.id))]) else: colref = pgast.ColumnRef( name=[common.edgedb_name_to_pg_name(str(node.typeref.id))]) return colref
def _new_mapped_pointer_rvar( ir_ptr: irast.Pointer, *, ctx: context.CompilerContextLevel) -> pgast.PathRangeVar: ptrref = ir_ptr.ptrref dml_source = irutils.get_nearest_dml_stmt(ir_ptr.source) ptr_rvar = range_for_pointer(ir_ptr, dml_source=dml_source, ctx=ctx) src_col = 'source' source_ref = pgast.ColumnRef(name=[src_col], nullable=False) if (irtyputils.is_object(ptrref.out_target) and not irtyputils.is_computable_ptrref(ptrref)): tgt_ptr_info = pg_types.get_ptrref_storage_info(ptrref, resolve_type=False) tgt_col = tgt_ptr_info.column_name else: tgt_col = 'target' target_ref = pgast.ColumnRef(name=[tgt_col], nullable=not ptrref.required) # Set up references according to the link direction. if ir_ptr.direction == s_pointers.PointerDirection.Inbound: near_ref = target_ref far_ref = source_ref else: near_ref = source_ref far_ref = target_ref src_pid = ir_ptr.source.path_id tgt_pid = ir_ptr.target.path_id ptr_pid = tgt_pid.ptr_path() ptr_rvar.query.path_id = ptr_pid pathctx.put_rvar_path_bond(ptr_rvar, src_pid) pathctx.put_rvar_path_output(ptr_rvar, src_pid, aspect='identity', var=near_ref, env=ctx.env) pathctx.put_rvar_path_output(ptr_rvar, src_pid, aspect='value', var=near_ref, env=ctx.env) pathctx.put_rvar_path_output(ptr_rvar, tgt_pid, aspect='value', var=far_ref, env=ctx.env) if tgt_pid.is_objtype_path(): pathctx.put_rvar_path_bond(ptr_rvar, tgt_pid) pathctx.put_rvar_path_output(ptr_rvar, tgt_pid, aspect='identity', var=far_ref, env=ctx.env) return ptr_rvar
def range_for_ptrref(ptrref: irast.BasePointerRef, *, include_overlays: bool = True, only_self: bool = False, env: context.Environment) -> pgast.BaseRangeVar: """"Return a Range subclass corresponding to a given ptr step. The return value may potentially be a UNION of all tables corresponding to a set of specialized links computed from the given `ptrref` taking source inheritance into account. """ tgt_col = pgtypes.get_ptrref_storage_info(ptrref, resolve_type=False, link_bias=True).column_name cols = ['source', tgt_col] set_ops = [] if only_self: ptrrefs = {ptrref} else: ptrrefs = {ptrref} | ptrref.descendants for src_ptrref in ptrrefs: table = table_from_ptrref(src_ptrref, env=env) qry = pgast.SelectStmt() qry.from_clause.append(table) qry.rptr_rvar = table # Make sure all property references are pulled up properly for colname in cols: selexpr = pgast.ColumnRef(name=[table.alias.aliasname, colname]) qry.target_list.append(pgast.ResTarget(val=selexpr, name=colname)) set_ops.append(('union', qry)) overlays = env.rel_overlays.get(src_ptrref.shortname) if overlays and include_overlays: for op, cte in overlays: rvar = pgast.RangeVar( relation=cte, alias=pgast.Alias(aliasname=env.aliases.get(cte.name))) qry = pgast.SelectStmt( target_list=[ pgast.ResTarget(val=pgast.ColumnRef(name=[col])) for col in cols ], from_clause=[rvar], ) set_ops.append((op, qry)) rvar = range_from_queryset(set_ops, ptrref.shortname, env=env) return rvar
def new_root_rvar(ir_set: irast.Set, *, typeref: Optional[irast.TypeRef] = None, ctx: context.CompilerContextLevel) -> pgast.PathRangeVar: if not ir_set.path_id.is_objtype_path(): raise ValueError('cannot create root rvar for non-object path') if typeref is None: typeref = ir_set.typeref if typeref.intersection: wrapper = pgast.SelectStmt() for component in typeref.intersection: component_rvar = new_root_rvar(ir_set, typeref=component, ctx=ctx) pathctx.put_rvar_path_bond(component_rvar, ir_set.path_id) include_rvar(wrapper, component_rvar, ir_set.path_id, ctx=ctx) return rvar_for_rel(wrapper, ctx=ctx) dml_source = irutils.get_nearest_dml_stmt(ir_set) set_rvar = range_for_typeref(typeref, ir_set.path_id, dml_source=dml_source, ctx=ctx) pathctx.put_rvar_path_bond(set_rvar, ir_set.path_id) set_rvar.query.value_scope.add(ir_set.path_id) if ir_set.rptr and ir_set.rptr.is_inbound: ptrref = ir_set.rptr.ptrref ptr_info = pg_types.get_ptrref_storage_info(ptrref, resolve_type=False, link_bias=False) if ptr_info.table_type == 'ObjectType': # Inline link prefix_path_id = ir_set.path_id.src_path() assert prefix_path_id is not None, 'expected a path' rref = pgast.ColumnRef(name=[ptr_info.column_name], nullable=not ptrref.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) astutils.for_each_query_in_set( set_rvar.query, lambda qry: qry.target_list.append( pgast.ResTarget( val=rref, name=ptr_info.column_name, ))) return set_rvar
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, ))
def new_pointer_rvar( ir_ptr: irast.Pointer, *, link_bias: bool=False, src_rvar: pgast.PathRangeVar, ctx: context.CompilerContextLevel) -> pgast.PathRangeVar: ptrref = ir_ptr.ptrref ptr_info = pg_types.get_ptrref_storage_info( ptrref, resolve_type=False, link_bias=link_bias) if ptr_info.table_type == 'ObjectType': # Inline link return _new_inline_pointer_rvar( ir_ptr, ptr_info=ptr_info, src_rvar=src_rvar, ctx=ctx) else: return _new_mapped_pointer_rvar(ir_ptr, ctx=ctx)
def semi_join(stmt: pgast.SelectStmt, ir_set: irast.Set, src_rvar: pgast.PathRangeVar, *, ctx: context.CompilerContextLevel) -> pgast.PathRangeVar: """Join an IR Set using semi-join.""" rptr = ir_set.rptr assert rptr is not None # Target set range. set_rvar = new_root_rvar(ir_set, ctx=ctx) ptrref = rptr.ptrref ptr_info = pg_types.get_ptrref_storage_info(ptrref, resolve_type=False, allow_missing=True) if ptr_info and ptr_info.table_type == 'ObjectType': if irtyputils.is_inbound_ptrref(ptrref): far_pid = ir_set.path_id.src_path() assert far_pid is not None else: far_pid = ir_set.path_id else: far_pid = ir_set.path_id # Link range. map_rvar = new_pointer_rvar(rptr, src_rvar=src_rvar, ctx=ctx) include_rvar(ctx.rel, map_rvar, path_id=ir_set.path_id.ptr_path(), ctx=ctx) tgt_ref = pathctx.get_rvar_path_identity_var(set_rvar, far_pid, env=ctx.env) pathctx.get_path_identity_output(ctx.rel, far_pid, env=ctx.env) cond = astutils.new_binop(tgt_ref, ctx.rel, 'IN') stmt.where_clause = astutils.extend_binop(stmt.where_clause, cond) return set_rvar
def new_root_rvar(ir_set: irast.Set, *, typeref: typing.Optional[irast.TypeRef] = None, ctx: context.CompilerContextLevel) -> pgast.BaseRangeVar: if not ir_set.path_id.is_objtype_path(): raise ValueError('cannot create root rvar for non-object path') if typeref is None: typeref = ir_set.typeref set_rvar = dbobj.range_for_typeref(typeref, ir_set.path_id, env=ctx.env) pathctx.put_rvar_path_bond(set_rvar, ir_set.path_id) set_rvar.value_scope.add(ir_set.path_id) if ir_set.rptr and ir_set.rptr.is_inbound: ptrref = ir_set.rptr.ptrref ptr_info = pg_types.get_ptrref_storage_info(ptrref, resolve_type=False, link_bias=False) if ptr_info.table_type == 'ObjectType': # Inline link rref = pgast.ColumnRef(name=[ptr_info.column_name], nullable=not ptrref.required) pathctx.put_rvar_path_bond(set_rvar, ir_set.path_id.src_path()) pathctx.put_rvar_path_output(set_rvar, ir_set.path_id.src_path(), aspect='identity', var=rref, env=ctx.env) if astutils.is_set_op_query(set_rvar.query): astutils.for_each_query_in_set( set_rvar.query, lambda qry: qry.target_list.append( pgast.ResTarget( val=rref, name=ptr_info.column_name, ))) return set_rvar
def _compile_set_in_singleton_mode( node: irast.Set, *, ctx: context.CompilerContextLevel) -> pgast.BaseExpr: if isinstance(node, irast.EmptySet): return pgast.NullConstant() elif node.expr is not None: return dispatch.compile(node.expr, ctx=ctx) else: if node.rptr: ptrref = node.rptr.ptrref source = node.rptr.source if isinstance(ptrref, irast.TupleIndirectionPointerRef): tuple_val = dispatch.compile(source, ctx=ctx) set_expr = astutils.tuple_getattr( tuple_val, source.typeref, ptrref.shortname.name, ) return set_expr if ptrref.source_ptr is None and source.rptr is not None: raise errors.UnsupportedFeatureError( 'unexpectedly long path in simple expr') ptr_stor_info = pg_types.get_ptrref_storage_info( ptrref, resolve_type=False) colref = pgast.ColumnRef( name=[ptr_stor_info.column_name], nullable=node.rptr.dir_cardinality.can_be_zero()) else: name = [common.edgedb_name_to_pg_name(str(node.typeref.id))] if node.path_id.is_objtype_path(): name.append('id') colref = pgast.ColumnRef(name=name) return colref
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 process_link_values( *, ir_stmt: irast.MutatingStmt, ir_expr: irast.Set, col_data: Mapping[str, pgast.BaseExpr], dml_rvar: pgast.PathRangeVar, sources: Iterable[pgast.BaseRangeVar], source_typeref: irast.TypeRef, target_is_scalar: bool, dml_cte: pgast.CommonTableExpr, iterator: Optional[pgast.IteratorCTE], ctx: context.CompilerContextLevel, ) -> Tuple[pgast.CommonTableExpr, List[str]]: """Unpack data from an update expression into a series of selects. :param ir_expr: IR of the INSERT/UPDATE body element. :param col_data: Expressions used to populate well-known columns of the link table such as `source` and `__type__`. :param sources: A list of relations which must be joined into the data query to resolve expressions in *col_data*. :param target_is_scalar: Whether the link target is an ScalarType. :param iterator: IR and CTE representing the iterator range in the FOR clause of the EdgeQL DML statement. """ old_dml_count = len(ctx.dml_stmts) with ctx.newscope() as newscope, newscope.newrel() as subrelctx: subrelctx.enclosing_cte_iterator = pgast.IteratorCTE( path_id=ir_stmt.subject.path_id, cte=dml_cte, parent=iterator, is_dml_pseudo_iterator=True) row_query = subrelctx.rel relctx.include_rvar(row_query, dml_rvar, path_id=ir_stmt.subject.path_id, ctx=subrelctx) subrelctx.path_scope[ir_stmt.subject.path_id] = row_query merge_iterator(iterator, row_query, ctx=subrelctx) with subrelctx.newscope() as sctx, sctx.subrel() as input_rel_ctx: input_rel = input_rel_ctx.rel input_rel_ctx.expr_exposed = False input_rel_ctx.volatility_ref = pathctx.get_path_identity_var( row_query, ir_stmt.subject.path_id, env=input_rel_ctx.env) dispatch.visit(ir_expr, ctx=input_rel_ctx) if (isinstance(ir_expr.expr, irast.Stmt) and ir_expr.expr.iterator_stmt is not None): # The link value is computaed by a FOR expression, # check if the statement is a DML statement, and if so, # pull the iterator scope so that link property expressions # have the correct context. inner_iterator_cte = None inner_iterator_path_id = ir_expr.expr.iterator_stmt.path_id for cte in input_rel_ctx.toplevel_stmt.ctes: if cte.query.path_id == inner_iterator_path_id: inner_iterator_cte = cte break if inner_iterator_cte is not None: inner_iterator_rvar = relctx.rvar_for_rel( inner_iterator_cte, lateral=True, ctx=subrelctx) relctx.include_rvar( input_rel, inner_iterator_rvar, path_id=inner_iterator_path_id, ctx=subrelctx, ) input_rel_ctx.path_scope[inner_iterator_path_id] = ( input_rel) shape_tuple = None if ir_expr.shape: shape_tuple = shapecomp.compile_shape( ir_expr, [expr for expr, _ in ir_expr.shape], ctx=input_rel_ctx, ) for element in shape_tuple.elements: pathctx.put_path_var_if_not_exists(input_rel_ctx.rel, element.path_id, element.val, aspect='value', env=input_rel_ctx.env) input_stmt: pgast.Query = input_rel input_rvar = pgast.RangeSubselect( subquery=input_rel, lateral=True, alias=pgast.Alias(aliasname=ctx.env.aliases.get('val'))) if len(ctx.dml_stmts) > old_dml_count: # If there were any nested inserts, we need to join them in. pathctx.put_rvar_path_bond(input_rvar, ir_stmt.subject.path_id) relctx.include_rvar(row_query, input_rvar, path_id=ir_stmt.subject.path_id, ctx=ctx) source_data: Dict[str, pgast.BaseExpr] = {} if isinstance(input_stmt, pgast.SelectStmt) and input_stmt.op is not None: # UNION input_stmt = input_stmt.rarg path_id = ir_expr.path_id if shape_tuple is not None: for element in shape_tuple.elements: if not element.path_id.is_linkprop_path(): continue val = pathctx.get_rvar_path_value_var(input_rvar, element.path_id, env=ctx.env) rptr = element.path_id.rptr() assert isinstance(rptr, irast.PointerRef) actual_rptr = irtyputils.find_actual_ptrref(source_typeref, rptr) ptr_info = pg_types.get_ptrref_storage_info(actual_rptr) source_data.setdefault(ptr_info.column_name, val) else: if target_is_scalar: target_ref = pathctx.get_rvar_path_value_var(input_rvar, path_id, env=ctx.env) else: target_ref = pathctx.get_rvar_path_identity_var(input_rvar, path_id, env=ctx.env) source_data['target'] = target_ref if not target_is_scalar and 'target' not in source_data: target_ref = pathctx.get_rvar_path_identity_var(input_rvar, path_id, env=ctx.env) source_data['target'] = target_ref specified_cols = [] for col, expr in collections.ChainMap(col_data, source_data).items(): row_query.target_list.append(pgast.ResTarget(val=expr, name=col)) specified_cols.append(col) row_query.from_clause += list(sources) link_rows = pgast.CommonTableExpr(query=row_query, name=ctx.env.aliases.get(hint='r')) return link_rows, specified_cols
def process_update_body(ir_stmt: irast.MutatingStmt, wrapper: pgast.Query, update_cte: pgast.CommonTableExpr, range_cte: pgast.CommonTableExpr, *, ctx: context.CompilerContextLevel): """Generate SQL DML CTEs from an UpdateStmt IR. :param ir_stmt: IR of the statement. :param wrapper: Top-level SQL query. :param update_cte: CTE representing the SQL UPDATE to the main relation of the Object. :param range_cte: CTE representing the range affected by the statement. """ update_stmt = update_cte.query assert isinstance(update_stmt, pgast.UpdateStmt) external_updates = [] toplevel = ctx.toplevel_stmt toplevel.ctes.append(range_cte) toplevel.ctes.append(update_cte) with ctx.newscope() as subctx: # It is necessary to process the expressions in # the UpdateStmt shape body in the context of the # UPDATE statement so that references to the current # values of the updated object are resolved correctly. subctx.path_scope[ir_stmt.subject.path_id] = update_stmt subctx.rel = update_stmt subctx.expr_exposed = False for shape_el in ir_stmt.subject.shape: ptrref = shape_el.rptr.ptrref updvalue = shape_el.expr ptr_info = pg_types.get_ptrref_storage_info(ptrref, resolve_type=True, link_bias=False) if ptr_info.table_type == 'ObjectType' and updvalue is not None: with subctx.newscope() as scopectx: # First, process all internal link updates updtarget = pgast.UpdateTarget( name=ptr_info.column_name, val=pgast.TypeCast(arg=dispatch.compile(updvalue, ctx=scopectx), type_name=pgast.TypeName( name=ptr_info.column_type))) update_stmt.targets.append(updtarget) props_only = is_props_only_update(shape_el, ctx=subctx) ptr_info = pg_types.get_ptrref_storage_info(ptrref, resolve_type=False, link_bias=True) if ptr_info and ptr_info.table_type == 'link': external_updates.append((shape_el, props_only)) if not update_stmt.targets: # No updates directly to the set target table, # so convert the UPDATE statement into a SELECT. from_clause: typing.List[pgast.BaseRangeVar] = [update_stmt.relation] from_clause.extend(update_stmt.from_clause) update_cte.query = pgast.SelectStmt( ctes=update_stmt.ctes, target_list=update_stmt.returning_list, from_clause=from_clause, where_clause=update_stmt.where_clause, path_namespace=update_stmt.path_namespace, path_outputs=update_stmt.path_outputs, path_scope=update_stmt.path_scope, path_rvar_map=update_stmt.path_rvar_map.copy(), view_path_id_map=update_stmt.view_path_id_map.copy(), ptr_join_map=update_stmt.ptr_join_map.copy(), ) # Process necessary updates to the link tables. for expr, props_only in external_updates: process_link_update(ir_stmt=ir_stmt, ir_set=expr, props_only=False, wrapper=wrapper, dml_cte=update_cte, iterator_cte=None, is_insert=False, ctx=ctx)
def range_for_ptrref( ptrref: irast.BasePointerRef, *, dml_source: Optional[irast.MutatingStmt] = None, for_mutation: bool = False, only_self: bool = False, ctx: context.CompilerContextLevel, ) -> pgast.PathRangeVar: """"Return a Range subclass corresponding to a given ptr step. The return value may potentially be a UNION of all tables corresponding to a set of specialized links computed from the given `ptrref` taking source inheritance into account. """ tgt_col = pg_types.get_ptrref_storage_info(ptrref, resolve_type=False, link_bias=True).column_name cols = ['source', tgt_col] set_ops = [] if ptrref.union_components: refs = ptrref.union_components if only_self and len(refs) > 1: raise errors.InternalServerError('unexpected union link') else: refs = {ptrref} assert isinstance(ptrref, irast.PointerRef), \ "expected regular PointerRef" overlays = get_ptr_rel_overlays(ptrref, dml_source=dml_source, ctx=ctx) for src_ptrref in refs: assert isinstance(src_ptrref, irast.PointerRef), \ "expected regular PointerRef" table = table_from_ptrref( src_ptrref, include_descendants=not ptrref.union_is_concrete, for_mutation=for_mutation, ctx=ctx, ) qry = pgast.SelectStmt() qry.from_clause.append(table) # Make sure all property references are pulled up properly for colname in cols: selexpr = pgast.ColumnRef(name=[table.alias.aliasname, colname]) qry.target_list.append(pgast.ResTarget(val=selexpr, name=colname)) set_ops.append(('union', qry)) overlays = get_ptr_rel_overlays(src_ptrref, dml_source=dml_source, ctx=ctx) if overlays and not for_mutation: for op, cte in overlays: rvar = pgast.RelRangeVar( relation=cte, alias=pgast.Alias(aliasname=ctx.env.aliases.get(cte.name))) qry = pgast.SelectStmt( target_list=[ pgast.ResTarget(val=pgast.ColumnRef(name=[col])) for col in cols ], from_clause=[rvar], ) set_ops.append((op, qry)) return range_from_queryset(set_ops, ptrref.shortname, ctx=ctx)
def process_update_body( ir_stmt: irast.MutatingStmt, wrapper: pgast.Query, update_cte: pgast.CommonTableExpr, typeref: irast.TypeRef, *, ctx: context.CompilerContextLevel, ) -> None: """Generate SQL DML CTEs from an UpdateStmt IR. :param ir_stmt: IR of the statement. :param wrapper: Top-level SQL query. :param update_cte: CTE representing the SQL UPDATE to the main relation of the Object. :param typeref: The specific TypeRef of a set being updated. """ update_stmt = update_cte.query assert isinstance(update_stmt, pgast.UpdateStmt) external_updates = [] with ctx.newscope() as subctx: # It is necessary to process the expressions in # the UpdateStmt shape body in the context of the # UPDATE statement so that references to the current # values of the updated object are resolved correctly. subctx.parent_rel = update_stmt subctx.expr_exposed = False for shape_el, shape_op in ir_stmt.subject.shape: ptrref = shape_el.rptr.ptrref updvalue = shape_el.expr ptr_info = pg_types.get_ptrref_storage_info(ptrref, resolve_type=True, link_bias=False) if ptr_info.table_type == 'ObjectType' and updvalue is not None: with subctx.newscope() as scopectx: val: pgast.BaseExpr if irtyputils.is_tuple(shape_el.typeref): # When target is a tuple type, make sure # the expression is compiled into a subquery # returning a single column that is explicitly # cast into the appropriate composite type. val = relgen.set_as_subquery( shape_el, as_value=True, explicit_cast=ptr_info.column_type, ctx=scopectx, ) else: if (isinstance(updvalue, irast.MutatingStmt) and updvalue in ctx.dml_stmts): with scopectx.substmt() as relctx: dml_cte = ctx.dml_stmts[updvalue] wrap_dml_cte(updvalue, dml_cte, ctx=relctx) pathctx.get_path_identity_output( relctx.rel, updvalue.subject.path_id, env=relctx.env, ) val = relctx.rel else: val = dispatch.compile(updvalue, ctx=scopectx) val = pgast.TypeCast(arg=val, type_name=pgast.TypeName( name=ptr_info.column_type)) if shape_op is qlast.ShapeOp.SUBTRACT: val = pgast.FuncCall( name=('nullif', ), args=[ pgast.ColumnRef(name=(ptr_info.column_name, )), val, ], ) updtarget = pgast.UpdateTarget( name=ptr_info.column_name, val=val, ) update_stmt.targets.append(updtarget) props_only = is_props_only_update(shape_el, ctx=subctx) ptr_info = pg_types.get_ptrref_storage_info(ptrref, resolve_type=False, link_bias=True) if ptr_info and ptr_info.table_type == 'link': external_updates.append((shape_el, shape_op, props_only)) if not update_stmt.targets: # No updates directly to the set target table, # so convert the UPDATE statement into a SELECT. from_clause: List[pgast.BaseRangeVar] = [update_stmt.relation] from_clause.extend(update_stmt.from_clause) update_cte.query = pgast.SelectStmt( ctes=update_stmt.ctes, target_list=update_stmt.returning_list, from_clause=from_clause, where_clause=update_stmt.where_clause, path_namespace=update_stmt.path_namespace, path_outputs=update_stmt.path_outputs, path_scope=update_stmt.path_scope, path_rvar_map=update_stmt.path_rvar_map.copy(), view_path_id_map=update_stmt.view_path_id_map.copy(), ptr_join_map=update_stmt.ptr_join_map.copy(), ) toplevel = ctx.toplevel_stmt toplevel.ctes.append(update_cte) # Process necessary updates to the link tables. for expr, shape_op, _ in external_updates: process_link_update( ir_stmt=ir_stmt, ir_set=expr, props_only=False, wrapper=wrapper, dml_cte=update_cte, iterator_cte=None, is_insert=False, shape_op=shape_op, source_typeref=typeref, ctx=ctx, )
def process_insert_body(ir_stmt: irast.MutatingStmt, wrapper: pgast.Query, insert_cte: pgast.CommonTableExpr, insert_rvar: pgast.PathRangeVar, *, ctx: context.CompilerContextLevel) -> None: """Generate SQL DML CTEs from an InsertStmt IR. :param ir_stmt: IR of the statement. :param wrapper: Top-level SQL query. :param insert_cte: CTE representing the SQL INSERT to the main relation of the Object. """ cols = [pgast.ColumnRef(name=['__type__'])] select = pgast.SelectStmt(target_list=[]) values = select.target_list # The main INSERT query of this statement will always be # present to insert at least the `id` and `__type__` # properties. insert_stmt = insert_cte.query assert isinstance(insert_stmt, pgast.InsertStmt) insert_stmt.cols = cols insert_stmt.select_stmt = select if ir_stmt.parent_stmt is not None: iterator_set = ir_stmt.parent_stmt.iterator_stmt else: iterator_set = None if iterator_set is not None: with ctx.substmt() as ictx: ictx.path_scope = ictx.path_scope.new_child() ictx.path_scope[iterator_set.path_id] = ictx.rel clauses.compile_iterator_expr(ictx.rel, iterator_set, ctx=ictx) ictx.rel.path_id = iterator_set.path_id pathctx.put_path_bond(ictx.rel, iterator_set.path_id) iterator_cte = pgast.CommonTableExpr( query=ictx.rel, name=ctx.env.aliases.get('iter')) ictx.toplevel_stmt.ctes.append(iterator_cte) iterator_rvar = relctx.rvar_for_rel(iterator_cte, ctx=ctx) relctx.include_rvar(select, iterator_rvar, path_id=ictx.rel.path_id, ctx=ctx) iterator_id = pathctx.get_path_identity_var(select, iterator_set.path_id, env=ctx.env) else: iterator_cte = None iterator_id = None typeref = ir_stmt.subject.typeref if typeref.material_type is not None: typeref = typeref.material_type values.append( pgast.ResTarget(val=pgast.TypeCast( arg=pgast.StringConstant(val=str(typeref.id)), type_name=pgast.TypeName(name=('uuid', ))), )) external_inserts = [] parent_link_props = [] with ctx.newrel() as subctx: subctx.rel = select subctx.rel_hierarchy[select] = insert_stmt subctx.expr_exposed = False if iterator_cte is not None: subctx.path_scope = ctx.path_scope.new_child() subctx.path_scope[iterator_cte.query.path_id] = select # Process the Insert IR and separate links that go # into the main table from links that are inserted into # a separate link table. for shape_el in ir_stmt.subject.shape: rptr = shape_el.rptr ptrref = rptr.ptrref if ptrref.material_ptr is not None: ptrref = ptrref.material_ptr if (ptrref.parent_ptr is not None and rptr.source.path_id != ir_stmt.subject.path_id): parent_link_props.append(shape_el) continue ptr_info = pg_types.get_ptrref_storage_info(ptrref, resolve_type=True, link_bias=False) props_only = False # First, process all local link inserts. if ptr_info.table_type == 'ObjectType': props_only = True field = pgast.ColumnRef(name=[ptr_info.column_name]) cols.append(field) insvalue = insert_value_for_shape_element(insert_stmt, wrapper, ir_stmt, shape_el, iterator_id, ptr_info=ptr_info, ctx=subctx) values.append(pgast.ResTarget(val=insvalue)) ptr_info = pg_types.get_ptrref_storage_info(ptrref, resolve_type=False, link_bias=True) if ptr_info and ptr_info.table_type == 'link': external_inserts.append((shape_el, props_only)) if iterator_cte is not None: cols.append(pgast.ColumnRef(name=['__edb_token'])) values.append(pgast.ResTarget(val=iterator_id)) pathctx.put_path_identity_var(insert_stmt, iterator_set.path_id, cols[-1], force=True, env=subctx.env) pathctx.put_path_bond(insert_stmt, iterator_set.path_id) toplevel = ctx.toplevel_stmt toplevel.ctes.append(insert_cte) # Process necessary updates to the link tables. for shape_el, props_only in external_inserts: process_link_update(ir_stmt=ir_stmt, ir_set=shape_el, props_only=props_only, wrapper=wrapper, dml_cte=insert_cte, iterator_cte=iterator_cte, is_insert=True, ctx=ctx) if parent_link_props: prop_elements = [] with ctx.newscope() as scopectx: scopectx.rel = wrapper for shape_el in parent_link_props: rptr = shape_el.rptr scopectx.path_scope[rptr.source.path_id] = wrapper pathctx.put_path_rvar_if_not_exists(wrapper, rptr.source.path_id, insert_rvar, aspect='value', env=scopectx.env) dispatch.visit(shape_el, ctx=scopectx) tuple_el = astutils.tuple_element_for_shape_el(shape_el, None, ctx=scopectx) prop_elements.append(tuple_el) valtuple = pgast.TupleVar(elements=prop_elements, named=True) pathctx.put_path_value_var(wrapper, ir_stmt.subject.path_id, valtuple, force=True, env=ctx.env)
def process_insert_body(ir_stmt: irast.MutatingStmt, wrapper: pgast.SelectStmt, insert_cte: pgast.CommonTableExpr, insert_rvar: pgast.PathRangeVar, *, ctx: context.CompilerContextLevel) -> None: """Generate SQL DML CTEs from an InsertStmt IR. :param ir_stmt: IR of the statement. :param wrapper: Top-level SQL query. :param insert_cte: CTE representing the SQL INSERT to the main relation of the Object. """ cols = [pgast.ColumnRef(name=['__type__'])] select = pgast.SelectStmt(target_list=[]) values = select.target_list # The main INSERT query of this statement will always be # present to insert at least the `id` and `__type__` # properties. insert_stmt = insert_cte.query assert isinstance(insert_stmt, pgast.InsertStmt) insert_stmt.cols = cols insert_stmt.select_stmt = select if ir_stmt.parent_stmt is not None: iterator_set = ir_stmt.parent_stmt.iterator_stmt else: iterator_set = None iterator_cte: Optional[pgast.CommonTableExpr] iterator_id: Optional[pgast.BaseExpr] if iterator_set is not None: with ctx.substmt() as ictx: ictx.path_scope = ictx.path_scope.new_child() ictx.path_scope[iterator_set.path_id] = ictx.rel clauses.compile_iterator_expr(ictx.rel, iterator_set, ctx=ictx) ictx.rel.path_id = iterator_set.path_id pathctx.put_path_bond(ictx.rel, iterator_set.path_id) iterator_cte = pgast.CommonTableExpr( query=ictx.rel, name=ctx.env.aliases.get('iter')) ictx.toplevel_stmt.ctes.append(iterator_cte) iterator_rvar = relctx.rvar_for_rel(iterator_cte, ctx=ctx) relctx.include_rvar(select, iterator_rvar, path_id=ictx.rel.path_id, ctx=ctx) iterator_id = pathctx.get_path_identity_var(select, iterator_set.path_id, env=ctx.env) else: iterator_cte = None iterator_id = None typeref = ir_stmt.subject.typeref if typeref.material_type is not None: typeref = typeref.material_type values.append( pgast.ResTarget(val=pgast.TypeCast( arg=pgast.StringConstant(val=str(typeref.id)), type_name=pgast.TypeName(name=('uuid', ))), )) external_inserts = [] with ctx.newrel() as subctx: subctx.rel = select subctx.rel_hierarchy[select] = insert_stmt subctx.expr_exposed = False if iterator_cte is not None: subctx.path_scope = ctx.path_scope.new_child() subctx.path_scope[iterator_cte.query.path_id] = select # Process the Insert IR and separate links that go # into the main table from links that are inserted into # a separate link table. for shape_el, shape_op in ir_stmt.subject.shape: assert shape_op is qlast.ShapeOp.ASSIGN rptr = shape_el.rptr ptrref = rptr.ptrref if ptrref.material_ptr is not None: ptrref = ptrref.material_ptr if (ptrref.source_ptr is not None and rptr.source.path_id != ir_stmt.subject.path_id): continue ptr_info = pg_types.get_ptrref_storage_info(ptrref, resolve_type=True, link_bias=False) props_only = False # First, process all local link inserts. if ptr_info.table_type == 'ObjectType': props_only = True field = pgast.ColumnRef(name=[ptr_info.column_name]) cols.append(field) rel = compile_insert_shape_element(insert_stmt, wrapper, ir_stmt, shape_el, iterator_id, ctx=ctx) insvalue = pathctx.get_path_value_var(rel, shape_el.path_id, env=ctx.env) if irtyputils.is_tuple(shape_el.typeref): # Tuples require an explicit cast. insvalue = pgast.TypeCast( arg=output.output_as_value(insvalue, env=ctx.env), type_name=pgast.TypeName(name=ptr_info.column_type, ), ) values.append(pgast.ResTarget(val=insvalue)) ptr_info = pg_types.get_ptrref_storage_info(ptrref, resolve_type=False, link_bias=True) if ptr_info and ptr_info.table_type == 'link': external_inserts.append((shape_el, props_only)) if iterator_set is not None: cols.append(pgast.ColumnRef(name=['__edb_token'])) values.append(pgast.ResTarget(val=iterator_id)) pathctx.put_path_identity_var(insert_stmt, iterator_set.path_id, cols[-1], force=True, env=subctx.env) pathctx.put_path_bond(insert_stmt, iterator_set.path_id) pathctx.put_path_rvar( wrapper, path_id=iterator_set.path_id, rvar=insert_rvar, aspect='identity', env=subctx.env, ) if isinstance(ir_stmt, irast.InsertStmt) and ir_stmt.on_conflict: assert not insert_stmt.on_conflict constraint_name = f'"{ir_stmt.on_conflict.id};schemaconstr"' insert_stmt.on_conflict = pgast.OnConflictClause( action='nothing', infer=pgast.InferClause(conname=constraint_name), ) toplevel = ctx.toplevel_stmt toplevel.ctes.append(insert_cte) # Process necessary updates to the link tables. for shape_el, props_only in external_inserts: process_link_update( ir_stmt=ir_stmt, ir_set=shape_el, props_only=props_only, wrapper=wrapper, dml_cte=insert_cte, source_typeref=typeref, iterator_cte=iterator_cte, is_insert=True, ctx=ctx, )
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
def range_for_ptrref( ptrref: irast.BasePointerRef, *, dml_source: Optional[irast.MutatingStmt] = None, for_mutation: bool = False, only_self: bool = False, ctx: context.CompilerContextLevel, ) -> pgast.PathRangeVar: """"Return a Range subclass corresponding to a given ptr step. The return value may potentially be a UNION of all tables corresponding to a set of specialized links computed from the given `ptrref` taking source inheritance into account. """ output_cols = ('source', 'target') set_ops = [] if ptrref.union_components: refs = ptrref.union_components if only_self and len(refs) > 1: raise errors.InternalServerError('unexpected union link') else: refs = {ptrref} assert isinstance(ptrref, irast.PointerRef), \ "expected regular PointerRef" overlays = get_ptr_rel_overlays(ptrref, dml_source=dml_source, ctx=ctx) for src_ptrref in refs: assert isinstance(src_ptrref, irast.PointerRef), \ "expected regular PointerRef" # Most references to inline links are dispatched to a separate # code path (_new_inline_pointer_rvar) by new_pointer_rvar, # but when we have union pointers, some might be inline. We # always use the link table if it exists (because this range # needs to contain any link properties, for one reason.) ptr_info = pg_types.get_ptrref_storage_info( src_ptrref, resolve_type=False, link_bias=True, ) if not ptr_info: assert ptrref.union_components ptr_info = pg_types.get_ptrref_storage_info( src_ptrref, resolve_type=False, link_bias=False, ) cols = [ 'source' if ptr_info.table_type == 'link' else 'id', ptr_info.column_name, ] table = table_from_ptrref( src_ptrref, ptr_info, include_descendants=not ptrref.union_is_concrete, for_mutation=for_mutation, ctx=ctx, ) qry = pgast.SelectStmt() qry.from_clause.append(table) # Make sure all property references are pulled up properly for colname, output_colname in zip(cols, output_cols): selexpr = pgast.ColumnRef(name=[table.alias.aliasname, colname]) qry.target_list.append( pgast.ResTarget(val=selexpr, name=output_colname)) set_ops.append(('union', qry)) overlays = get_ptr_rel_overlays(src_ptrref, dml_source=dml_source, ctx=ctx) if overlays and not for_mutation: for op, cte in overlays: rvar = pgast.RelRangeVar( relation=cte, alias=pgast.Alias(aliasname=ctx.env.aliases.get(cte.name))) qry = pgast.SelectStmt( target_list=[ pgast.ResTarget(val=pgast.ColumnRef(name=[col])) for col in cols ], from_clause=[rvar], ) set_ops.append((op, qry)) return range_from_queryset(set_ops, ptrref.shortname, ctx=ctx)
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, 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 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 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 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_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