Exemple #1
0
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)
Exemple #2
0
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)
Exemple #3
0
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
Exemple #4
0
    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
Exemple #5
0
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,
            ))
Exemple #6
0
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