def ensure_transient_identity_for_path(path_id: irast.PathId, stmt: pgast.BaseRelation, *, ctx: context.CompilerContextLevel, type: str = 'int') -> None: if type == 'uuid': id_expr = pgast.FuncCall( name=( 'edgedbext', 'uuid_generate_v1mc', ), args=[], ) else: id_expr = pgast.FuncCall(name=('row_number', ), args=[], over=pgast.WindowDef()) pathctx.put_path_identity_var(stmt, path_id, id_expr, force=True, env=ctx.env) pathctx.put_path_bond(stmt, path_id) if isinstance(stmt, pgast.SelectStmt): apply_volatility_ref(stmt, ctx=ctx)
def ensure_transient_identity_for_set(ir_set: irast.Set, stmt: pgast.Query, *, ctx: context.CompilerContextLevel, type='int') -> None: if type == 'uuid': id_expr = pgast.FuncCall( name=( 'edgedb', 'uuid_generate_v1mc', ), args=[], ) else: id_expr = pgast.FuncCall(name=('row_number', ), args=[], over=pgast.WindowDef()) pathctx.put_path_identity_var(stmt, ir_set.path_id, id_expr, force=True, env=ctx.env) pathctx.put_path_bond(stmt, ir_set.path_id)
def get_volatility_ref( path_id: irast.PathId, stmt: pgast.SelectStmt, *, ctx: context.CompilerContextLevel) -> Optional[pgast.BaseExpr]: """Produce an appropriate volatility_ref from a path_id.""" ref: Optional[pgast.BaseExpr] = relctx.maybe_get_path_var( stmt, path_id, aspect='identity', ctx=ctx) if not ref: rvar = relctx.maybe_get_path_rvar(stmt, path_id, aspect='value', ctx=ctx) if rvar and isinstance(rvar.query, pgast.ReturningQuery): # If we are selecting from a nontrivial subquery, manually # add a volatility ref based on row_number. We do it # manually because the row number isn't /really/ the # identity of the set. name = ctx.env.aliases.get('key') rvar.query.target_list.append( pgast.ResTarget(name=name, val=pgast.FuncCall(name=('row_number', ), args=[], over=pgast.WindowDef()))) ref = pgast.ColumnRef(name=[rvar.alias.aliasname, name]) else: ref = relctx.maybe_get_path_var(stmt, path_id, aspect='value', ctx=ctx) return ref
def _get_volatility_ref() -> Optional[pgast.BaseExpr]: nonlocal vol_ref if vol_ref: return vol_ref name = ctx.env.aliases.get('key') grouprel.target_list.append( pgast.ResTarget(name=name, val=pgast.FuncCall(name=('row_number', ), args=[], over=pgast.WindowDef()))) vol_ref = pgast.ColumnRef(name=[group_rvar.alias.aliasname, name]) return vol_ref
def ensure_transient_identity_for_set(ir_set: irast.Set, stmt: pgast.BaseRelation, *, ctx: context.CompilerContextLevel, type: str = 'int') -> None: if type == 'uuid': id_expr = pgast.FuncCall( name=( 'edgedbext', 'uuid_generate_v1mc', ), args=[], ) else: id_expr = pgast.FuncCall(name=('row_number', ), args=[], over=pgast.WindowDef()) pathctx.put_path_identity_var(stmt, ir_set.path_id, id_expr, force=True, env=ctx.env) pathctx.put_path_bond(stmt, ir_set.path_id) if (ctx.volatility_ref is not None and ctx.volatility_ref is not context.NO_VOLATILITY and isinstance(stmt, pgast.SelectStmt)): # Apply the volatility reference. # See the comment in process_set_as_subquery(). stmt.where_clause = astutils.extend_binop( stmt.where_clause, pgast.NullTest( arg=ctx.volatility_ref, negated=True, ))
def compile_GroupStmt(stmt: irast.GroupStmt, *, ctx: context.CompilerContextLevel) -> pgast.Query: parent_ctx = ctx with parent_ctx.substmt() as ctx: clauses.init_stmt(stmt, ctx=ctx, parent_ctx=parent_ctx) group_path_id = stmt.group_path_id # Process the GROUP .. BY part into a subquery. with ctx.subrel() as gctx: gctx.expr_exposed = False gquery = gctx.rel pathctx.put_path_bond(gquery, group_path_id) relctx.update_scope(stmt.subject, gquery, ctx=gctx) stmt.subject.path_scope = None clauses.compile_output(stmt.subject, ctx=gctx) subj_rvar = pathctx.get_path_rvar(gquery, stmt.subject.path_id, aspect='value', env=gctx.env) relctx.ensure_bond_for_expr(stmt.subject, subj_rvar.query, ctx=gctx) group_paths = set() part_clause: List[pgast.BaseExpr] = [] for ir_gexpr in stmt.groupby: with gctx.new() as subctx: partexpr = dispatch.compile(ir_gexpr, ctx=subctx) part_clause.append(partexpr) group_paths.add(ir_gexpr.path_id) # Since we will be computing arbitrary expressions # based on the grouped sets, it is more efficient # to compute the "group bond" as a small unique # value than it is to use GROUP BY and aggregate # actual id values into an array. # # To achieve this we use the first_value() window # function while using the GROUP BY clause as # a partition clause. We use the id of the first # object in each partition if GROUP BY input is # a ObjectType, otherwise we generate the id using # row_number(). if stmt.subject.path_id.is_objtype_path(): first_val = pathctx.get_path_identity_var(gquery, stmt.subject.path_id, env=ctx.env) else: with ctx.subrel() as subctx: wrapper = subctx.rel gquery_rvar = relctx.rvar_for_rel(gquery, ctx=subctx) wrapper.from_clause = [gquery_rvar] relctx.pull_path_namespace(target=wrapper, source=gquery_rvar, ctx=subctx) new_part_clause: List[pgast.BaseExpr] = [] for i, expr in enumerate(part_clause): path_id = stmt.groupby[i].path_id pathctx.put_path_value_var(gquery, path_id, expr, force=True, env=ctx.env) output_ref = pathctx.get_path_value_output(gquery, path_id, env=ctx.env) assert isinstance(output_ref, pgast.ColumnRef) new_part_clause.append( astutils.get_column(gquery_rvar, output_ref)) part_clause = new_part_clause first_val = pathctx.get_rvar_path_identity_var( gquery_rvar, stmt.subject.path_id, env=ctx.env) gquery = wrapper pathctx.put_path_bond(gquery, group_path_id) group_id = pgast.FuncCall( name=('first_value', ), args=[first_val], over=pgast.WindowDef(partition_clause=part_clause)) pathctx.put_path_identity_var(gquery, group_path_id, group_id, env=ctx.env) pathctx.put_path_value_var(gquery, group_path_id, group_id, env=ctx.env) group_cte = pgast.CommonTableExpr(query=gquery, name=ctx.env.aliases.get('g')) group_cte_rvar = relctx.rvar_for_rel(group_cte, ctx=ctx) # Generate another subquery contaning distinct values of # path expressions in BY. with ctx.subrel() as gvctx: gvquery = gvctx.rel relctx.include_rvar(gvquery, group_cte_rvar, path_id=group_path_id, ctx=gvctx) pathctx.put_path_bond(gvquery, group_path_id) for group_set in stmt.groupby: dispatch.visit(group_set, ctx=gvctx) path_id = group_set.path_id if path_id.is_objtype_path(): pathctx.put_path_bond(gvquery, path_id) gvquery.distinct_clause = [ pathctx.get_path_identity_var(gvquery, group_path_id, env=ctx.env) ] for path_id, aspect in list(gvquery.path_rvar_map): if path_id not in group_paths and path_id != group_path_id: gvquery.path_rvar_map.pop((path_id, aspect)) for path_id, aspect in list(gquery.path_rvar_map): if path_id in group_paths: gquery.path_rvar_map.pop((path_id, aspect)) gquery.path_namespace.pop((path_id, aspect), None) gquery.path_outputs.pop((path_id, aspect), None) groupval_cte = pgast.CommonTableExpr(query=gvquery, name=ctx.env.aliases.get('gv')) groupval_cte_rvar = relctx.rvar_for_rel(groupval_cte, ctx=ctx) o_stmt = stmt.result.expr assert isinstance(o_stmt, irast.SelectStmt) # process the result expression; with ctx.subrel() as selctx: selquery = selctx.rel outer_id = stmt.result.path_id inner_id = o_stmt.result.path_id relctx.include_specific_rvar(selquery, groupval_cte_rvar, group_path_id, aspects=['identity'], ctx=ctx) for path_id in group_paths: selctx.path_scope[path_id] = selquery pathctx.put_path_rvar(selquery, path_id, groupval_cte_rvar, aspect='value', env=ctx.env) selctx.group_by_rels = selctx.group_by_rels.copy() selctx.group_by_rels[group_path_id, stmt.subject.path_id] = \ group_cte selquery.view_path_id_map = {outer_id: inner_id} selquery.ctes.append(group_cte) sortoutputs = [] selquery.ctes.append(groupval_cte) clauses.compile_output(o_stmt.result, ctx=selctx) # The WHERE clause if o_stmt.where is not None: selquery.where_clause = astutils.extend_binop( selquery.where_clause, clauses.compile_filter_clause(o_stmt.where, o_stmt.where_card, ctx=selctx)) for ir_sortexpr in o_stmt.orderby: alias = ctx.env.aliases.get('s') sexpr = dispatch.compile(ir_sortexpr.expr, ctx=selctx) selquery.target_list.append( pgast.ResTarget(val=sexpr, name=alias)) sortoutputs.append(alias) if not gvquery.target_list: # No values were pulled from the group-values rel, # we must remove the DISTINCT clause to prevent # a syntax error. gvquery.distinct_clause[:] = [] query = ctx.rel result_rvar = relctx.rvar_for_rel(selquery, lateral=True, ctx=ctx) relctx.include_rvar(query, result_rvar, path_id=outer_id, ctx=ctx) for rt in selquery.target_list: if rt.name is None: rt.name = ctx.env.aliases.get('v') if rt.name not in sortoutputs: query.target_list.append( pgast.ResTarget(val=astutils.get_column( result_rvar, rt.name), name=rt.name)) for i, ir_oexpr in enumerate(o_stmt.orderby): sort_ref = astutils.get_column(result_rvar, sortoutputs[i]) sortexpr = pgast.SortBy(node=sort_ref, dir=ir_oexpr.direction, nulls=ir_oexpr.nones_order) query.sort_clause.append(sortexpr) # The OFFSET clause if o_stmt.offset: with ctx.new() as ctx1: ctx1.expr_exposed = False query.limit_offset = dispatch.compile(o_stmt.offset, ctx=ctx1) # The LIMIT clause if o_stmt.limit: with ctx.new() as ctx1: ctx1.expr_exposed = False query.limit_count = dispatch.compile(o_stmt.limit, ctx=ctx1) clauses.fini_stmt(query, ctx, parent_ctx) return query