def rel_join( query: pgast.Query, right_rvar: pgast.BaseRangeVar, *, ctx: context.CompilerContextLevel) -> None: condition = None for path_id in right_rvar.path_scope: lref = maybe_get_path_var(query, path_id, aspect='identity', ctx=ctx) if lref is None: lref = maybe_get_path_var(query, path_id, aspect='value', ctx=ctx) if lref is None: continue rref = pathctx.get_rvar_path_identity_var( right_rvar, path_id, env=ctx.env) path_cond = astutils.join_condition(lref, rref) condition = astutils.extend_binop(condition, path_cond) if condition is None: join_type = 'cross' else: join_type = 'inner' if not query.from_clause: query.from_clause.append(right_rvar) if condition is not None: query.where_clause = astutils.extend_binop( query.where_clause, condition) else: larg = query.from_clause[0] rarg = right_rvar query.from_clause[0] = pgast.JoinExpr( type=join_type, larg=larg, rarg=rarg, quals=condition)
def get_path_output_or_null( rel: pgast.Query, path_id: irast.PathId, *, aspect: str, env: context.Environment) -> \ typing.Tuple[pgast.OutputVar, bool]: path_id = map_path_id(path_id, rel.view_path_id_map) ref = maybe_get_path_output(rel, path_id, aspect=aspect, env=env) if ref is not None: return ref, False alt_aspect = get_less_specific_aspect(path_id, aspect) if alt_aspect is not None: ref = maybe_get_path_output(rel, path_id, aspect=alt_aspect, env=env) if ref is not None: rel.path_outputs[path_id, aspect] = ref return ref, False alias = env.aliases.get('null') restarget = pgast.ResTarget(name=alias, val=pgast.Constant(val=None)) if hasattr(rel, 'returning_list'): rel.returning_list.append(restarget) else: rel.target_list.append(restarget) ref = pgast.ColumnRef(name=[alias], nullable=True) rel.path_outputs[path_id, aspect] = ref return ref, True
def fini_dml_stmt(ir_stmt: irast.MutatingStmt, wrapper: pgast.Query, dml_cte: pgast.CommonTableExpr, dml_rvar: pgast.BaseRangeVar, *, parent_ctx: context.CompilerContextLevel, ctx: context.CompilerContextLevel) -> pgast.Query: # Record the effect of this insertion in the relation overlay # context to ensure that the RETURNING clause potentially # referencing this class yields the expected results. if isinstance(ir_stmt, irast.InsertStmt): dbobj.add_rel_overlay(ir_stmt.subject.scls, 'union', dml_cte, env=ctx.env) elif isinstance(ir_stmt, irast.DeleteStmt): dbobj.add_rel_overlay(ir_stmt.subject.scls, 'except', dml_cte, env=ctx.env) if parent_ctx.toplevel_stmt is wrapper: ret_ref = pathctx.get_path_identity_var(wrapper, ir_stmt.subject.path_id, env=parent_ctx.env) count = pgast.FuncCall(name=('count', ), args=[ret_ref]) wrapper.target_list = [pgast.ResTarget(val=count)] clauses.fini_stmt(wrapper, ctx, parent_ctx) return wrapper
def get_path_serialized_output(rel: pgast.Query, path_id: irast.PathId, *, env: context.Environment) -> pgast.OutputVar: # Serialized output is a special case, we don't # want this behaviour to be recursive, so it # must be kept outside of get_path_output() generic. aspect = 'serialized' result = rel.path_outputs.get((path_id, aspect)) if result is not None: return result ref = get_path_serialized_or_value_var(rel, path_id, env=env) ref = output.serialize_expr(ref, path_id=path_id, env=env) alias = get_path_output_alias(path_id, aspect, env=env) restarget = pgast.ResTarget(name=alias, val=ref) if hasattr(rel, 'returning_list'): rel.returning_list.append(restarget) else: rel.target_list.append(restarget) result = pgast.ColumnRef(name=[alias], nullable=ref.nullable) rel.path_outputs[path_id, aspect] = result return result
def semi_join(stmt: pgast.Query, ir_set: irast.Set, src_rvar: pgast.BaseRangeVar, *, ctx: context.CompilerContextLevel) -> pgast.BaseRangeVar: """Join an IR Set using semi-join.""" rptr = ir_set.rptr ptrcls = rptr.ptrcls ptr_info = pg_types.get_pointer_storage_info(ptrcls, resolve_type=False, link_bias=False) is_inline_ref = ptr_info.table_type == 'ObjectType' # Target set range. set_rvar = new_root_rvar(ir_set, ctx=ctx) # Link range. map_rvar = new_pointer_rvar(rptr, src_rvar=src_rvar, ctx=ctx) # Target identity in the target range. if rptr.is_inbound and is_inline_ref: tgt_pid = ir_set.path_id.extend(ptrcls) else: tgt_pid = ir_set.path_id tgt_ref = pathctx.get_rvar_path_identity_var(set_rvar, tgt_pid, env=ctx.env) include_rvar(ctx.rel, map_rvar, path_id=ir_set.path_id.ptr_path(), ctx=ctx) pathctx.get_path_identity_output(ctx.rel, ir_set.path_id, 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 top_output_as_value(stmt: pgast.Query, *, env: context.Environment) -> pgast.Query: """Finalize output serialization on the top level.""" if env.output_format == context.OutputFormat.JSON: # For JSON we just want to aggregate the whole thing # into a JSON array. subrvar = pgast.RangeSubselect( subquery=stmt, alias=pgast.Alias(aliasname=env.aliases.get('aggw'))) stmt_res = stmt.target_list[0] if stmt_res.name is None: stmt_res.name = env.aliases.get('v') new_val = pgast.FuncCall(name=('json_agg', ), args=[pgast.ColumnRef(name=[stmt_res.name])]) # XXX: nullability introspection is not reliable, # remove `True or` once it is. if True or stmt_res.val.nullable: new_val = pgast.CoalesceExpr( args=[new_val, pgast.Constant(val='[]')]) result = pgast.SelectStmt(target_list=[pgast.ResTarget(val=new_val)], from_clause=[subrvar]) result.ctes = stmt.ctes stmt.ctes = [] return result else: return stmt
def put_path_var(rel: pgast.Query, path_id: irast.PathId, var: pgast.Base, *, aspect: str, force: bool = False, env: context.Environment) -> None: if (path_id, aspect) in rel.path_namespace and not force: raise KeyError(f'{aspect} of {path_id} is already present in {rel}') rel.path_namespace[path_id, aspect] = var
def put_path_rvar(stmt: pgast.Query, path_id: irast.PathId, rvar: pgast.BaseRangeVar, *, aspect: str, env: context.Environment) -> None: assert isinstance(path_id, irast.PathId) stmt.path_rvar_map[path_id, aspect] = rvar # Normally, masked paths (i.e paths that are only behind a fence below), # will not be exposed in a query namespace. However, when the masked # path in the *main* path of a set, it must still be exposed, but no # further than the immediate parent query. if path_id in rvar.query.path_id_mask: stmt.path_id_mask.add(path_id)
def fini_stmt(stmt: pgast.Query, ctx: context.CompilerContextLevel, parent_ctx: context.CompilerContextLevel) -> None: if stmt is ctx.toplevel_stmt: stmt.argnames = ctx.argmap