Thread: Removing another gen_node_support.pl special case
I got confused about how we were managing EquivalenceClass pointers in the copy/equal infrastructure, and it took me awhile to remember that the reason it works is that gen_node_support.pl has hard-wired knowledge about that. I think that's something we'd be best off dropping in favor of explicit annotations on affected fields. Hence, I propose the attached. This results in zero change in the generated copy/equal code. regards, tom lane diff --git a/src/backend/nodes/gen_node_support.pl b/src/backend/nodes/gen_node_support.pl index b6f086e262..9f7b4b833f 100644 --- a/src/backend/nodes/gen_node_support.pl +++ b/src/backend/nodes/gen_node_support.pl @@ -166,9 +166,6 @@ my @custom_read_write; # Track node types with manually assigned NodeTag numbers. my %manual_nodetag_number; -# EquivalenceClasses are never moved, so just shallow-copy the pointer -push @scalar_types, qw(EquivalenceClass* EquivalenceMember*); - # This is a struct, so we can copy it by assignment. Equal support is # currently not required. push @scalar_types, qw(QualCost); @@ -458,9 +455,14 @@ foreach my $infile (@ARGV) && $attr !~ /^copy_as\(\w+\)$/ && $attr !~ /^read_as\(\w+\)$/ && !elem $attr, - qw(equal_ignore equal_ignore_if_zero read_write_ignore - write_only_relids write_only_nondefault_pathtarget write_only_req_outer) - ) + qw(copy_as_scalar + equal_as_scalar + equal_ignore + equal_ignore_if_zero + read_write_ignore + write_only_relids + write_only_nondefault_pathtarget + write_only_req_outer)) { die "$infile:$lineno: unrecognized attribute \"$attr\"\n"; @@ -692,6 +694,8 @@ _equal${n}(const $n *a, const $n *b) # extract per-field attributes my $array_size_field; my $copy_as_field; + my $copy_as_scalar = 0; + my $equal_as_scalar = 0; foreach my $a (@a) { if ($a =~ /^array_size\(([\w.]+)\)$/) @@ -702,19 +706,41 @@ _equal${n}(const $n *a, const $n *b) { $copy_as_field = $1; } + elsif ($a eq 'copy_as_scalar') + { + $copy_as_scalar = 1; + } + elsif ($a eq 'equal_as_scalar') + { + $equal_as_scalar = 1; + } elsif ($a eq 'equal_ignore') { $equal_ignore = 1; } } - # override type-specific copy method if copy_as is specified + # override type-specific copy method if requested if (defined $copy_as_field) { print $cff "\tnewnode->$f = $copy_as_field;\n" unless $copy_ignore; $copy_ignore = 1; } + elsif ($copy_as_scalar) + { + print $cff "\tCOPY_SCALAR_FIELD($f);\n" + unless $copy_ignore; + $copy_ignore = 1; + } + + # override type-specific equal method if requested + if ($equal_as_scalar) + { + print $eff "\tCOMPARE_SCALAR_FIELD($f);\n" + unless $equal_ignore; + $equal_ignore = 1; + } # select instructions by field type if ($t eq 'char*') diff --git a/src/include/nodes/nodes.h b/src/include/nodes/nodes.h index a80f43e540..d98f2c91d9 100644 --- a/src/include/nodes/nodes.h +++ b/src/include/nodes/nodes.h @@ -86,6 +86,12 @@ typedef enum NodeTag * * - copy_as(VALUE): In copyObject(), replace the field's value with VALUE. * + * - copy_as_scalar: In copyObject(), copy the field's pointer value + * even if it is a node-type pointer. + * + * - equal_as_scalar: In equal(), compare the field by pointer equality + * even if it is a node-type pointer. + * * - equal_ignore: Ignore the field for equality. * * - equal_ignore_if_zero: Ignore the field for equality if it is zero. diff --git a/src/include/nodes/pathnodes.h b/src/include/nodes/pathnodes.h index a544b313d3..885ad42327 100644 --- a/src/include/nodes/pathnodes.h +++ b/src/include/nodes/pathnodes.h @@ -1273,7 +1273,9 @@ typedef struct StatisticExtInfo * * NB: EquivalenceClasses are never copied after creation. Therefore, * copyObject() copies pointers to them as pointers, and equal() compares - * pointers to EquivalenceClasses via pointer equality. + * pointers to EquivalenceClasses via pointer equality. This is implemented + * by putting copy_as_scalar and equal_as_scalar attributes on fields that + * are pointers to EquivalenceClasses. The same goes for EquivalenceMembers. */ typedef struct EquivalenceClass { @@ -1364,7 +1366,8 @@ typedef struct PathKey NodeTag type; - EquivalenceClass *pk_eclass; /* the value that is ordered */ + /* the value that is ordered */ + EquivalenceClass *pk_eclass pg_node_attr(copy_as_scalar, equal_as_scalar); Oid pk_opfamily; /* btree opfamily defining the ordering */ int pk_strategy; /* sort direction (ASC or DESC) */ bool pk_nulls_first; /* do NULLs come before normal values? */ @@ -2472,7 +2475,7 @@ typedef struct RestrictInfo * Generating EquivalenceClass. This field is NULL unless clause is * potentially redundant. */ - EquivalenceClass *parent_ec pg_node_attr(equal_ignore, read_write_ignore); + EquivalenceClass *parent_ec pg_node_attr(copy_as_scalar, equal_ignore, read_write_ignore); /* * cache space for cost and selectivity @@ -2500,13 +2503,13 @@ typedef struct RestrictInfo */ /* EquivalenceClass containing lefthand */ - EquivalenceClass *left_ec pg_node_attr(equal_ignore, read_write_ignore); + EquivalenceClass *left_ec pg_node_attr(copy_as_scalar, equal_ignore, read_write_ignore); /* EquivalenceClass containing righthand */ - EquivalenceClass *right_ec pg_node_attr(equal_ignore, read_write_ignore); + EquivalenceClass *right_ec pg_node_attr(copy_as_scalar, equal_ignore, read_write_ignore); /* EquivalenceMember for lefthand */ - EquivalenceMember *left_em pg_node_attr(equal_ignore); + EquivalenceMember *left_em pg_node_attr(copy_as_scalar, equal_ignore); /* EquivalenceMember for righthand */ - EquivalenceMember *right_em pg_node_attr(equal_ignore); + EquivalenceMember *right_em pg_node_attr(copy_as_scalar, equal_ignore); /* * List of MergeScanSelCache structs. Those aren't Nodes, so hard to
On 27.11.22 02:39, Tom Lane wrote: > I got confused about how we were managing EquivalenceClass pointers > in the copy/equal infrastructure, and it took me awhile to remember > that the reason it works is that gen_node_support.pl has hard-wired > knowledge about that. I think that's something we'd be best off > dropping in favor of explicit annotations on affected fields. > Hence, I propose the attached. This results in zero change in > the generated copy/equal code. I suppose the question is whether this behavior is something that is a property of the EquivalenceClass type as such or something that is specific to each individual field.
Peter Eisentraut <peter.eisentraut@enterprisedb.com> writes: > On 27.11.22 02:39, Tom Lane wrote: >> I got confused about how we were managing EquivalenceClass pointers >> in the copy/equal infrastructure, and it took me awhile to remember >> that the reason it works is that gen_node_support.pl has hard-wired >> knowledge about that. I think that's something we'd be best off >> dropping in favor of explicit annotations on affected fields. >> Hence, I propose the attached. This results in zero change in >> the generated copy/equal code. > I suppose the question is whether this behavior is something that is a > property of the EquivalenceClass type as such or something that is > specific to each individual field. That's an interesting point, but what I'm on about is that I don't want the behavior buried in gen_node_support.pl. I think there's a reasonable argument to be made that equal_as_scalar *is* a field-level property not a node-level property. I agree that for the copy case you could argue it differently, and I also agree that it seems error-prone to have to remember to label fields this way. I notice that EquivalenceClass is already marked as no_copy_equal, which means that gen_node_support.pl can know that emitting a recursive node-copy or node-compare request is a bad idea. What do you think of using the patch as it stands, plus a cross-check that we don't emit COPY_NODE_FIELD or COMPARE_NODE_FIELD if the target node type is no_copy or no_equal? This is different from just silently applying scalar copy/equal, in that (a) it's visibly under the programmer's control, and (b) it's not hard to imagine wanting to use other solutions such as copy_as(NULL). (More generally, I suspect that there are other useful cross-checks gen_node_support.pl could be making. I had a to-do item to think about that, but it didn't get to the top of the list yet.) regards, tom lane
I wrote: > I notice that EquivalenceClass is already marked as no_copy_equal, > which means that gen_node_support.pl can know that emitting a > recursive node-copy or node-compare request is a bad idea. What > do you think of using the patch as it stands, plus a cross-check > that we don't emit COPY_NODE_FIELD or COMPARE_NODE_FIELD if the > target node type is no_copy or no_equal? Concretely, it seems like something like the attached could be useful, independently of the other change. regards, tom lane diff --git a/src/backend/nodes/gen_node_support.pl b/src/backend/nodes/gen_node_support.pl index b6f086e262..fc5b6721d6 100644 --- a/src/backend/nodes/gen_node_support.pl +++ b/src/backend/nodes/gen_node_support.pl @@ -123,6 +123,8 @@ my @no_equal; my @no_read; # node types we don't want read/write support for my @no_read_write; +# node types that have handmade read/write support +my @special_read_write; # node types we don't want any support functions for, just node tags my @nodetag_only; @@ -152,9 +154,12 @@ my @extra_tags = qw( # since we won't use its internal structure here anyway. push @node_types, qw(List); # Lists are specially treated in all four support files, too. -push @no_copy, qw(List); -push @no_equal, qw(List); -push @no_read_write, qw(List); +# (Ideally we'd mark List as "special copy/equal" not "no copy/equal". +# But until there's other use-cases for that, just hot-wire the tests +# that would need to distinguish.) +push @no_copy, qw(List); +push @no_equal, qw(List); +push @special_read_write, qw(List); # Nodes with custom copy/equal implementations are skipped from # .funcs.c but need case statements in .switch.c. @@ -338,16 +343,7 @@ foreach my $infile (@ARGV) } elsif ($attr eq 'special_read_write') { - # This attribute is called - # "special_read_write" because there is - # special treatment in outNode() and - # nodeRead() for these nodes. For this - # script, it's the same as - # "no_read_write", but calling the - # attribute that externally would probably - # be confusing, since read/write support - # does in fact exist. - push @no_read_write, $in_struct; + push @special_read_write, $in_struct; } elsif ($attr =~ /^nodetag_number\((\d+)\)$/) { @@ -786,6 +782,17 @@ _equal${n}(const $n *a, const $n *b) elsif (($t =~ /^(\w+)\*$/ or $t =~ /^struct\s+(\w+)\*$/) and elem $1, @node_types) { + die + "node type \"$1\" lacks copy support, which is required for struct \"$n\" field \"$f\"\n" + if (elem $1, @no_copy or elem $1, @nodetag_only) + and $1 ne 'List' + and !$copy_ignore; + die + "node type \"$1\" lacks equal support, which is required for struct \"$n\" field \"$f\"\n" + if (elem $1, @no_equal or elem $1, @nodetag_only) + and $1 ne 'List' + and !$equal_ignore; + print $cff "\tCOPY_NODE_FIELD($f);\n" unless $copy_ignore; print $eff "\tCOMPARE_NODE_FIELD($f);\n" unless $equal_ignore; } @@ -851,6 +858,7 @@ foreach my $n (@node_types) next if elem $n, @abstract_types; next if elem $n, @nodetag_only; next if elem $n, @no_read_write; + next if elem $n, @special_read_write; my $no_read = (elem $n, @no_read); @@ -1083,6 +1091,14 @@ _read${n}(void) elsif (($t =~ /^(\w+)\*$/ or $t =~ /^struct\s+(\w+)\*$/) and elem $1, @node_types) { + die + "node type \"$1\" lacks write support, which is required for struct \"$n\" field \"$f\"\n" + if (elem $1, @no_read_write or elem $1, @nodetag_only); + die + "node type \"$1\" lacks read support, which is required for struct \"$n\" field \"$f\"\n" + if (elem $1, @no_read or elem $1, @nodetag_only) + and !$no_read; + print $off "\tWRITE_NODE_FIELD($f);\n"; print $rff "\tREAD_NODE_FIELD($f);\n" unless $no_read; }
On 29.11.22 22:34, Tom Lane wrote: > I wrote: >> I notice that EquivalenceClass is already marked as no_copy_equal, >> which means that gen_node_support.pl can know that emitting a >> recursive node-copy or node-compare request is a bad idea. What >> do you think of using the patch as it stands, plus a cross-check >> that we don't emit COPY_NODE_FIELD or COMPARE_NODE_FIELD if the >> target node type is no_copy or no_equal? > > Concretely, it seems like something like the attached could be > useful, independently of the other change. Yes, right now you can easily declare things that don't make sense. Cross-checks like these look useful.
Peter Eisentraut <peter.eisentraut@enterprisedb.com> writes: > On 29.11.22 22:34, Tom Lane wrote: >> Concretely, it seems like something like the attached could be >> useful, independently of the other change. > Yes, right now you can easily declare things that don't make sense. > Cross-checks like these look useful. Checking my notes from awhile back, there was one other cross-check that I thought was pretty high-priority: verifying that array_size fields precede their array fields. Without that, a read function will fail entirely, and a compare function might index off the end of an array depending on which array-size field it chooses to believe. It seems like an easy mistake to make, too. I added that and pushed. regards, tom lane