Re: Adding OLD/NEW support to RETURNING
On Wed, 2024-03-27 at 13:19 +, Dean Rasheed wrote: > What I'm most worried about now is that there are other areas of > functionality like that, that I'm overlooking, and which will > interact > with this feature in non-trivial ways. Agreed. I'm not sure exactly how we'd find those other areas (if they exist) aside from just having more eyes on the code. > > So on reflection, rather than trying to rush to get this into v17, I > think it would be better to leave it to v18. Thank you for letting me know. That allows some time for others to have a look. Regards, Jeff Davis
Re: Adding OLD/NEW support to RETURNING
On Wed, 27 Mar 2024 at 07:47, Jeff Davis wrote: > > This isn't a complete review, but I spent a while looking at this, and > it looks like it's in good shape. Thanks for looking. > I like the syntax, and I think the solution for renaming the alias > ("RETURNING WITH (new as n, old as o)") is a good one. Thanks, that's good to know. Settling on a good syntax can be difficult, so it's good to know that people are generally supportive of this. > The implementation touches quite a few areas. How did you identify all > of the potential problem areas? Hmm, well that's one of the hardest parts, and it's really difficult to be sure that I have. Initially, when I was just adding a new field to Var, I just tried to look at all the existing code that made Vars, or copied other non-default fields like varnullingrels around. I still managed to miss the necessary change in assign_param_for_var() on my first attempt, but fortunately that was an easy fix. More worrying was the fact that I managed to completely overlook the fact that I needed to worry about non-updatable columns in auto-updatable views until v6, which added the ReturningExpr node. Once I realised that I needed that, and that it needed to be tied to a particular query level, and so needed a "levelsup" field, I just looked at GroupingFunc to identify the places in code that needed to be updated to do the right thing for a query-level-aware node. What I'm most worried about now is that there are other areas of functionality like that, that I'm overlooking, and which will interact with this feature in non-trivial ways. > It seems the primary sources of > complexity came from rules, partitioning, and updatable views, is that > right? Foreign tables looked like it would be tricky at first, but then turned out to be trivial, after disallowing direct-modify when returning old/new. Rules are a whole area that I wish I didn't have to worry about (I wish we had deprecated them a long time ago). In practice though, I haven't done much beyond what seemed like the most obvious (and simplest) thing. Nonetheless, there are some interesting interactions that probably need more careful examination. For example, the fact that the RETURNING clause in a RULE already has its own "special table names" OLD and NEW, which are actually references to different RTEs, unlike the OLD and NEW that this patch introduces, which are references to the result relation. This leads to a number of different cases: Case 1 == In the simplest case, the rule can simply contain "RETURNING *". This leads to what I think is the most obvious and intuitive behaviour: DROP TABLE IF EXISTS t1, t2 CASCADE; CREATE TABLE t1 (val1 text); INSERT INTO t1 VALUES ('Old value 1'); CREATE TABLE t2 (val2 text); INSERT INTO t2 VALUES ('Old value 2'); CREATE RULE r2 AS ON UPDATE TO t2 DO INSTEAD UPDATE t1 SET val1 = NEW.val2 RETURNING *; UPDATE t2 SET val2 = 'New value 2' RETURNING old.val2 AS old_val2, new.val2 AS new_val2, t2.val2 AS t2_val2, val2; old_val2 | new_val2 | t2_val2 |val2 -+-+-+- Old value 1 | New value 2 | New value 2 | New value 2 (1 row) So someone using the table with the rule can access old and new values in the obvious way, and they will get new values by default for an UPDATE. The query plan for this is pretty-much what you'd expect: QUERY PLAN --- Update on public.t1 Output: old.val1, new.val1, t1.val1, t1.val1 -> Nested Loop Output: 'New value 2'::text, t1.ctid, t2.ctid -> Seq Scan on public.t1 Output: t1.ctid -> Materialize Output: t2.ctid -> Seq Scan on public.t2 Output: t2.ctid Case 2 == If the rule contains "RETURNING OLD.*", it means that the RETURNING list of the rewritten query contains Vars that no longer refer to the result relation, but instead refer to the old data in t2. This leads the the following behaviour: DROP TABLE IF EXISTS t1, t2 CASCADE; CREATE TABLE t1 (val1 text); INSERT INTO t1 VALUES ('Old value 1'); CREATE TABLE t2 (val2 text); INSERT INTO t2 VALUES ('Old value 2'); CREATE RULE r2 AS ON UPDATE TO t2 DO INSTEAD UPDATE t1 SET val1 = NEW.val2 RETURNING OLD.*; UPDATE t2 SET val2 = 'New value 2' RETURNING old.val2 AS old_val2, new.val2 AS new_val2, t2.val2 AS t2_val2, val2; old_val2 | new_val2 | t2_val2 |val2 -+-+-+- Old value 2 | Old value 2 | Old value 2 | Old value 2 (1 row) The reason this happens is that the Vars in the returning list don't refer to the result relation, and so setting varreturningtype on them has no effect, and is simply ignored. This can be seen by looking at the query plan: QUERY PLAN
Re: Adding OLD/NEW support to RETURNING
On Tue, 2024-03-26 at 18:49 +, Dean Rasheed wrote: > On Mon, 25 Mar 2024 at 14:04, Dean Rasheed > wrote: > > > > v7 patch attached, with those updates. > > > > Rebased version attached, forced by 87985cc925. This isn't a complete review, but I spent a while looking at this, and it looks like it's in good shape. I like the syntax, and I think the solution for renaming the alias ("RETURNING WITH (new as n, old as o)") is a good one. The implementation touches quite a few areas. How did you identify all of the potential problem areas? It seems the primary sources of complexity came from rules, partitioning, and updatable views, is that right? Regards, Jeff Davis
Re: Adding OLD/NEW support to RETURNING
On Mon, Mar 18, 2024 at 6:48 PM Dean Rasheed wrote: > > On Tue, 12 Mar 2024 at 18:21, Dean Rasheed wrote: > > > > Updated version attached tidying up a couple of things and fixing another > > bug: > > > > Rebased version attached, on top of c649fa24a4 (MERGE ... RETURNING support). > hi, some minor issues I found out. +/* + * ReplaceReturningVarsFromTargetList - + * replace RETURNING list Vars with items from a targetlist + * + * This is equivalent to calling ReplaceVarsFromTargetList() with a + * nomatch_option of REPLACEVARS_REPORT_ERROR, but with the added effect of + * copying varreturningtype onto any Vars referring to new_result_relation, + * allowing RETURNING OLD/NEW to work in the rewritten query. + */ + +typedef struct +{ + ReplaceVarsFromTargetList_context rv_con; + int new_result_relation; +} ReplaceReturningVarsFromTargetList_context; + +static Node * +ReplaceReturningVarsFromTargetList_callback(Var *var, + replace_rte_variables_context *context) +{ + ReplaceReturningVarsFromTargetList_context *rcon = (ReplaceReturningVarsFromTargetList_context *) context->callback_arg; + Node *newnode; + + newnode = ReplaceVarsFromTargetList_callback(var, context); + + if (var->varreturningtype != VAR_RETURNING_DEFAULT) + SetVarReturningType((Node *) newnode, rcon->new_result_relation, + var->varlevelsup, var->varreturningtype); + + return newnode; +} + +Node * +ReplaceReturningVarsFromTargetList(Node *node, + int target_varno, int sublevels_up, + RangeTblEntry *target_rte, + List *targetlist, + int new_result_relation, + bool *outer_hasSubLinks) +{ + ReplaceReturningVarsFromTargetList_context context; + + context.rv_con.target_rte = target_rte; + context.rv_con.targetlist = targetlist; + context.rv_con.nomatch_option = REPLACEVARS_REPORT_ERROR; + context.rv_con.nomatch_varno = 0; + context.new_result_relation = new_result_relation; + + return replace_rte_variables(node, target_varno, sublevels_up, + ReplaceReturningVarsFromTargetList_callback, + (void *) , + outer_hasSubLinks); +} the ReplaceReturningVarsFromTargetList related comment should be placed right above the function ReplaceReturningVarsFromTargetList, not above ReplaceReturningVarsFromTargetList_context? struct ReplaceReturningVarsFromTargetList_context adds some comments about new_result_relation would be great. /* INDEX_VAR is handled by default case */ this comment appears in execExpr.c and execExprInterp.c. need to move to default case's switch default case? /* * set_deparse_context_plan - Specify Plan node containing expression * * When deparsing an expression in a Plan tree, we might have to resolve * OUTER_VAR, INNER_VAR, or INDEX_VAR references. To do this, the caller must * provide the parent Plan node. ... */ does the comment in set_deparse_context_plan need to be updated? + * buildNSItemForReturning - + * add a ParseNamespaceItem for the OLD or NEW alias in RETURNING. + */ +static void +addNSItemForReturning(ParseState *pstate, const char *aliasname, + VarReturningType returning_type) comment "buildNSItemForReturning" should be "addNSItemForReturning"? * results. If include_dropped is true then empty strings and NULL constants * (not Vars!) are returned for dropped columns. * - * rtindex, sublevels_up, and location are the varno, varlevelsup, and location - * values to use in the created Vars. Ordinarily rtindex should match the - * actual position of the RTE in its rangetable. + * rtindex, sublevels_up, returning_type, and location are the varno, + * varlevelsup, varreturningtype, and location values to use in the created + * Vars. Ordinarily rtindex should match the actual position of the RTE in + * its rangetable. we already updated the comment in expandRTE. but it seems we only do RTE_RELATION, some part of RTE_FUNCTION. do we need ` varnode->varreturningtype = returning_type; ` for other `rte->rtekind` when there is a makeVar? (I don't understand this part, in the case where rte->rtekind is RTE_SUBQUERY, if I add `varnode->varreturningtype = returning_type;` the tests still pass.
Re: Adding OLD/NEW support to RETURNING
On Sat, Mar 9, 2024 at 3:53 AM Dean Rasheed wrote: > > > Attached is a new patch, now with docs (no other code changes). > Hi, some issues I found, while playing around with support-returning-old-new-v2.patch doc/src/sgml/ref/update.sgml: [ RETURNING [ WITH ( { OLD | NEW } AS output_alias [, ...] ) ] * | output_expression [ [ AS ] output_name ] [, ...] ] There is no parameter explanation for `*`. so, I think the synopsis may not cover cases like: ` update foo set f3 = 443 RETURNING new.*; ` I saw the explanation at output_alias, though. - insert into foo select 1, 2 RETURNING old.*, new.f2, old.f1(); ERROR: function old.f1() does not exist LINE 1: ...sert into foo select 1, 2 RETURNING old.*, new.f2, old.f1(); ^ HINT: No function matches the given name and argument types. You might need to add explicit type casts. I guess that's ok, slightly different context evaluation. if you say "old.f1", old refers to the virtual table "old", but "old.f1()", the "old" , reevaluate to the schema "old". you need privilege to schema "old", you also need execution privilege to function "old.f1()" to execute the above query. so seems no security issue after all. - I found a fancy expression: ` CREATE TABLE foo (f1 serial, f2 text, f3 int default 42); insert into foo select 1, 2 union select 11, 22 RETURNING old.*, new.f2, (select sum(new.f1) over()); ` is this ok? also the following works on PG16, not sure it's a bug. ` insert into foo select 1, 2 union select 11, 22 RETURNING (select count(*)); ` but not these ` insert into foo select 1, 2 union select 11, 22 RETURNING (select count(old.*)); insert into foo select 1, 2 union select 11, 22 RETURNING (select sum(f1)); ` - I found another interesting case, while trying to add some tests on for new code in createplan.c. in postgres_fdw.sql, right after line `MERGE ought to fail cleanly` --this will work insert into itrtest select 1, 'foo' returning new.*,old.*; --these two will fail insert into remp1 select 1, 'foo' returning new.*; insert into remp1 select 1, 'foo' returning old.*; itrtest is the partitioned non-foreign table. remp1 is the partition of itrtest, foreign table. -- I did find a segment fault bug. insert into foo select 1, 2 RETURNING (select sum(old.f1) over()); This information is set in a subplan node. /* update the ExprState's flags if Var refers to OLD/NEW */ if (variable->varreturningtype == VAR_RETURNING_OLD) state->flags |= EEO_FLAG_HAS_OLD; else if (variable->varreturningtype == VAR_RETURNING_NEW) state->flags |= EEO_FLAG_HAS_NEW; but in ExecInsert: ` else if (resultRelInfo->ri_projectReturning->pi_state.flags & EEO_FLAG_HAS_OLD) { oldSlot = ExecGetReturningSlot(estate, resultRelInfo); ExecStoreAllNullTuple(oldSlot); oldSlot->tts_tableOid = RelationGetRelid(resultRelInfo->ri_RelationDesc); } ` it didn't use subplan node state->flags information. so the ExecInsert above code, never called, and should be executed. however ` insert into foo select 1, 2 RETURNING (select sum(new.f1)over());` works Similarly this ` delete from foo RETURNING (select sum(new.f1) over()); ` also causes segmentation fault. -- diff --git a/src/include/executor/tuptable.h b/src/include/executor/tuptable.h new file mode 100644 index 6133dbc..c9d3661 --- a/src/include/executor/tuptable.h +++ b/src/include/executor/tuptable.h @@ -411,12 +411,21 @@ slot_getsysattr(TupleTableSlot *slot, in { Assert(attnum < 0); /* caller error */ + /* + * If the tid is not valid, there is no physical row, and all system + * attributes are deemed to be NULL, except for the tableoid. + */ if (attnum == TableOidAttributeNumber) { *isnull = false; return ObjectIdGetDatum(slot->tts_tableOid); } - else if (attnum == SelfItemPointerAttributeNumber) + if (!ItemPointerIsValid(>tts_tid)) + { + *isnull = true; + return PointerGetDatum(NULL); + } + if (attnum == SelfItemPointerAttributeNumber) { *isnull = false; return PointerGetDatum(>tts_tid); These changes is slot_getsysattr is somehow independ of this feature?
Re: Adding OLD/NEW support to RETURNING
On 12/4/23 13:14, Dean Rasheed wrote: > I have been playing around with the idea of adding support for OLD/NEW > to RETURNING, partly motivated by the discussion on the MERGE > RETURNING thread [1], but also because I think it would be a very > useful addition for other commands (UPDATE in particular). > Sounds reasonable ... > This was discussed a long time ago [2], but that previous discussion > didn't lead to a workable patch, and so I have taken a different > approach here. > Presumably the 2013 thread went nowhere because of some implementation problems, not simply because the author lost interest and disappeared? Would it be helpful for this new patch to briefly summarize what the main issues were and how this new approach deals with that? (It's hard to say if reading the old thread is necessary/helpful for understanding this new patch, and time is a scarce resource.) > My first thought was that this would only really make sense for UPDATE > and MERGE, since OLD/NEW are pretty pointless for INSERT/DELETE > respectively. However... > > 1. For an INSERT with an ON CONFLICT ... DO UPDATE clause, returning > OLD might be very useful, since it provides a way to see which rows > conflicted, and return the old conflicting values. > > 2. If a DELETE is turned into an UPDATE by a rule (e.g., to mark rows > as deleted, rather than actually deleting them), then returning NEW > can also be useful. (I admit, this is a somewhat obscure use case, but > it's still possible.) > > 3. In a MERGE, we need to be able to handle all 3 command types anyway. > > 4. It really isn't any extra effort to support INSERT and DELETE. > > So in the attached very rough patch (no docs, minimal testing) I have > just allowed OLD/NEW in RETURNING for all command types (except, I > haven't done MERGE here - I think that's best kept as a separate > patch). If there is no OLD/NEW row in a particular context, it just > returns NULLs. The regression tests contain examples of 1 & 2 above. > > > Based on Robert Haas' suggestion in [2], the patch works by adding a > new "varreturningtype" field to Var nodes. This field is set during > parse analysis of the returning clause, which adds new namespace > aliases for OLD and NEW, if tables with those names/aliases are not > already present. So the resulting Var nodes have the same > varno/varattno as they would normally have had, but a different > varreturningtype. > No opinion on whether varreturningtype is the right approach - it sounds like it's working better than the 2013 patch, but I won't pretend my knowledge of this code is sufficient to make judgments beyond that. > For the most part, the rewriter and parser are then untouched, except > for a couple of places necessary to ensure that the new field makes it > through correctly. In particular, none of this affects the shape of > the final plan produced. All of the work to support the new Var > returning type is done in the executor. > > This turns out to be relatively straightforward, except for > cross-partition updates, which was a little trickier since the tuple > format of the old row isn't necessarily compatible with the new row, > which is in a different partition table and so might have a different > column order. > So we just "remap" the attributes, right? > One thing that I've explicitly disallowed is returning OLD/NEW for > updates to foreign tables. It's possible that could be added in a > later patch, but I have no plans to support that right now. > Sounds like an acceptable restriction, as long as it's documented. What are the challenges for supporting OLD/NEW for foreign tables? I guess we'd need to ask the FDW handler to tell us if it can support OLD/NEW for this table (and only allow it for postgres_fdw with sufficiently new server version), and then deparse the SQL. I'm asking because this seems like a nice first patch idea, but if I don't see some major obstacle that I don't see ... > > One difficult question is what names to use for the new aliases. I > think OLD and NEW are the most obvious and natural choices. However, > there is a problem - if they are used in a trigger function, they will > conflict. In PL/pgSQL, this leads to an error like the following: > > ERROR: column reference "new.f1" is ambiguous > LINE 3: RETURNING new.f1, new.f4 > ^ > DETAIL: It could refer to either a PL/pgSQL variable or a table column. > > That's the same error that you'd get if a different alias name had > been chosen, and it happened to conflict with a user-defined PL/pgSQL > variable, except that in that case, the user could just change their > variable name to fix the problem, which is not possible with the > automatically-a
Re: Adding OLD/NEW support to RETURNING
On Mon, Dec 4, 2023 at 8:15 PM Dean Rasheed wrote: > > I have been playing around with the idea of adding support for OLD/NEW > to RETURNING, partly motivated by the discussion on the MERGE > RETURNING thread [1], but also because I think it would be a very > useful addition for other commands (UPDATE in particular). > > This was discussed a long time ago [2], but that previous discussion > didn't lead to a workable patch, and so I have taken a different > approach here. > > Thoughts? > /* get the tuple from the relation being scanned */ - scratch.opcode = EEOP_ASSIGN_SCAN_VAR; + switch (variable->varreturningtype) + { + case VAR_RETURNING_OLD: + scratch.opcode = EEOP_ASSIGN_OLD_VAR; + break; + case VAR_RETURNING_NEW: + scratch.opcode = EEOP_ASSIGN_NEW_VAR; + break; + default: + scratch.opcode = EEOP_ASSIGN_SCAN_VAR; + break; + } I have roughly an idea of what this code is doing. but do you need to refactor the above comment? /* for EEOP_INNER/OUTER/SCAN_FETCHSOME */ in src/backend/executor/execExpr.c, do you need to update the comment? create temp table foo (f1 int, f2 int); insert into foo values (1,2), (3,4); INSERT INTO foo select 11, 22 RETURNING WITH (old AS new, new AS old) new.*, old.*; --this works. which is fine. create or replace function stricttest1() returns void as $$ declare x record; begin insert into foo values(5,6) returning new.* into x; raise notice 'x.f1 = % x.f2 %', x.f1, x.f2; end$$ language plpgsql; select * from stricttest1(); --this works. create or replace function stricttest2() returns void as $$ declare x record; y record; begin INSERT INTO foo select 11, 22 RETURNING WITH (old AS o, new AS n) o into x, n into y; raise notice 'x.f1: % x.f2 % y.f1 % y.f2 %', x.f1,x.f2, y.f1, y.f2; end$$ language plpgsql; --this does not work. --because https://www.postgresql.org/message-id/flat/CAFj8pRB76FE2MVxJYPc1RvXmsf2upoTgoPCC9GsvSAssCM2APQ%40mail.gmail.com create or replace function stricttest3() returns void as $$ declare x record; y record; begin INSERT INTO foo select 11, 22 RETURNING WITH (old AS o, new AS n) o.*,n.* into x; raise notice 'x.f1 % x.f2 %, % %', x.f1, x.f2, x.f1,x.f2; end$$ language plpgsql; select * from stricttest3(); --this is not what we want. because old and new share the same column name --so here you cannot get the "new" content. create or replace function stricttest4() returns void as $$ declare x record; y record; begin INSERT INTO foo select 11, 22 RETURNING WITH (old AS o, new AS n) o.f1 as of1,o.f2 as of2,n.f1 as nf1, n.f2 as nf2 into x; raise notice 'x.0f1 % x.of2 % nf1 % nf2 %', x.of1, x.of2, x.nf1, x.nf2; end$$ language plpgsql; --kind of verbose, but works, which is fine. create or replace function stricttest5() returns void as $$ declare x record; y record; a foo%ROWTYPE; b foo%ROWTYPE; begin INSERT INTO foo select 11, 22 RETURNING WITH (old AS o, new AS n) o into a, n into b; end$$ language plpgsql; -- expect this to work.
Adding OLD/NEW support to RETURNING
I have been playing around with the idea of adding support for OLD/NEW to RETURNING, partly motivated by the discussion on the MERGE RETURNING thread [1], but also because I think it would be a very useful addition for other commands (UPDATE in particular). This was discussed a long time ago [2], but that previous discussion didn't lead to a workable patch, and so I have taken a different approach here. My first thought was that this would only really make sense for UPDATE and MERGE, since OLD/NEW are pretty pointless for INSERT/DELETE respectively. However... 1. For an INSERT with an ON CONFLICT ... DO UPDATE clause, returning OLD might be very useful, since it provides a way to see which rows conflicted, and return the old conflicting values. 2. If a DELETE is turned into an UPDATE by a rule (e.g., to mark rows as deleted, rather than actually deleting them), then returning NEW can also be useful. (I admit, this is a somewhat obscure use case, but it's still possible.) 3. In a MERGE, we need to be able to handle all 3 command types anyway. 4. It really isn't any extra effort to support INSERT and DELETE. So in the attached very rough patch (no docs, minimal testing) I have just allowed OLD/NEW in RETURNING for all command types (except, I haven't done MERGE here - I think that's best kept as a separate patch). If there is no OLD/NEW row in a particular context, it just returns NULLs. The regression tests contain examples of 1 & 2 above. Based on Robert Haas' suggestion in [2], the patch works by adding a new "varreturningtype" field to Var nodes. This field is set during parse analysis of the returning clause, which adds new namespace aliases for OLD and NEW, if tables with those names/aliases are not already present. So the resulting Var nodes have the same varno/varattno as they would normally have had, but a different varreturningtype. For the most part, the rewriter and parser are then untouched, except for a couple of places necessary to ensure that the new field makes it through correctly. In particular, none of this affects the shape of the final plan produced. All of the work to support the new Var returning type is done in the executor. This turns out to be relatively straightforward, except for cross-partition updates, which was a little trickier since the tuple format of the old row isn't necessarily compatible with the new row, which is in a different partition table and so might have a different column order. One thing that I've explicitly disallowed is returning OLD/NEW for updates to foreign tables. It's possible that could be added in a later patch, but I have no plans to support that right now. One difficult question is what names to use for the new aliases. I think OLD and NEW are the most obvious and natural choices. However, there is a problem - if they are used in a trigger function, they will conflict. In PL/pgSQL, this leads to an error like the following: ERROR: column reference "new.f1" is ambiguous LINE 3: RETURNING new.f1, new.f4 ^ DETAIL: It could refer to either a PL/pgSQL variable or a table column. That's the same error that you'd get if a different alias name had been chosen, and it happened to conflict with a user-defined PL/pgSQL variable, except that in that case, the user could just change their variable name to fix the problem, which is not possible with the automatically-added OLD/NEW trigger variables. As a way round that, I added a way to optionally change the alias used in the RETURNING list, using the following syntax: RETURNING [ WITH ( { OLD | NEW } AS output_alias [, ...] ) ] * | output_expression [ [ AS ] output_name ] [, ...] for example: RETURNING WITH (OLD AS o) o.id, o.val, ... I'm not sure how good a solution that is, but the syntax doesn't look too bad to me (somewhat reminiscent of a WITH-query), and it's only necessary in cases where there is a name conflict. The simpler solution would be to just pick different alias names to start with. The previous thread seemed to settle on BEFORE/AFTER, but I don't find those names particularly intuitive or appealing. Over on [1], PREVIOUS/CURRENT was suggested, which I prefer, but they still don't seem as natural as OLD/NEW. So, as is often the case, naming things turns out to be the hardest problem, which is why I quite like the idea of letting the user pick their own name, if they need to. In most contexts, OLD and NEW will work, so they won't need to. Thoughts? Regards, Dean [1] https://www.postgresql.org/message-id/flat/CAEZATCWePEGQR5LBn-vD6SfeLZafzEm2Qy_L_Oky2=qw2w3...@mail.gmail.com [2] https://www.postgresql.org/message-id/flat/51822C0F.5030807%40gmail.com diff --git a/src/backend/executor/execExpr.c b/src/backend/executor/execExpr.c new file mode 100644 index 2c62b0c..7f6f2c5 --- a/src/backend/executor/execExpr.c +++ b/src/backend/executor/execExpr.c @@ -55,10 +55,15 @@ typedef struct ExprSetupInfo { - /* H