Re: deep copy with mutation? - Mailing list pgsql-hackers

From Ashutosh Bapat
Subject Re: deep copy with mutation?
Date
Msg-id CAExHW5tCSu83r0gQ_EBC4FMKF1FOxvgJ29WQzX=yeNp+O_8NiQ@mail.gmail.com
Whole thread
In response to deep copy with mutation?  (Robert Haas <robertmhaas@gmail.com>)
List pgsql-hackers
On Thu, Apr 30, 2026 at 2:52 AM Robert Haas <robertmhaas@gmail.com> wrote:
>
> I spent a lot of time today being stupid before finally understanding
> that expression_tree_mutator() and query_tree_mutator() can't be used
> to substitute for copyObject() because they deep copy structure only
> if it's Node structure; non-nodes like plain old C strings are not
> deep-copied. But then I wondered why we don't have a thing that works
> that way, because we have stuff like this in a number of places:
>
>             parsetree->returningList = copyObject(parsetree->returningList);
>             ChangeVarNodes((Node *) parsetree->returningList, rt_index,
>                            parsetree->resultRelation, 0);
>
> If we had a fully-deeply-copying version of copyObject(), it could
> just adjust new Var nodes as it created them, instead of needing a
> separate tree walk.
>
> In CreateTriggerFiringOn, we have:
>
>             qual = copyObject(whenClause);
>             qual = (Node *)
>                 map_partition_varattnos((List *) qual, PRS2_OLD_VARNO,
>                                         childTbl, rel);
>             qual = (Node *)
>                 map_partition_varattnos((List *) qual, PRS2_NEW_VARNO,
>                                         childTbl, rel);
>
> So a copy and then two consecutive mutation passes.
>
> Or similarly in rewriteTargetView:
>
>          tmp_tlist = copyObject(view_targetlist);
>
>          ChangeVarNodes((Node *) tmp_tlist, new_rt_index,
>                         new_exclRelIndex, 0);
>
>          parsetree->onConflict = (OnConflictExpr *)
>              ReplaceVarsFromTargetList((Node *) parsetree->onConflict,
>                                        old_exclRelIndex,
>                                        0,
>                                        view_rte,
>                                        tmp_tlist,
>                                        new_rt_index,
>                                        REPLACEVARS_REPORT_ERROR,
>                                        0,
>                                        &parsetree->hasSubLinks);
>
> I'm not entirely certain how much this kind of thing matters -- maybe
> it's better to have the copy routine be as simple as possible and
> accept the cost of walking the whole tree immediately afterwards to
> make changes? Perhaps this kind of thing is so cache-friendly that the
> mutation pass is really cheap? But it certainly kinda *looks*
> inefficient...

It looks inefficient in terms of CPU as well as memory since FLATCOPY
itself does palloc_object() and I see some mutators using
copyObject(). So we are copying the whole tree node by node and then
parts of the tree are copied by mutators. I think what's needed is
instead of FLATCOPY we need a version of copyObject which just creates
a copy of the node without copying the subtrees but copying the
structures like C strings and arrays. copyObject() already copies
arrays and C strings. Add a flag to copyObject to indicate whether we
want copy subtree or not and then replace FLATCOPY with
copyObject(node, false) and all existing copyObject as
copyObject(node, true). When generating _copy* functions, we define
COPY_NODE_FIELD as COPY_SCALAR_FIELD. Would that work?

--
Best Wishes,
Ashutosh Bapat



pgsql-hackers by date:

Previous
From: "cca5507"
Date:
Subject: Re: Make transformAExprIn() return a flattened bool expression directly
Next
From: Sergei Patiakin
Date:
Subject: Re: Inconsistent trigger behavior between two temporal leftovers