[PATCH] trenary reloption type

Started by Nikolay Shaplovabout 1 year ago13 messageshackers
Beta feature

Hackorum builds and tests every patch posted to the lists, not only commitfest submissions. This is Hackorum's own CI rather than the PostgreSQL project's, and it is still under testing - please report anything that looks wrong.

won't retrysuccessCI history

You can run a PostgreSQL built from this patch straight from Docker, with no checkout and no build:

docker run --rm -p 5432:5432 ghcr.io/hackorum-dev/postgres-patch:t52208
psql -h localhost -U postgres

Built from patchset v10 (message #10), August 18, 2026 at 02:02 PM.

Every patchset is also pushed to a branch of our PostgreSQL fork, so you can check out the same tree CI built. Without a PostgreSQL checkout:

git clone --branch t52208_10 https://github.com/hackorum-dev/postgres.git

In a checkout you already have, add the fork once:

git remote add hackorum https://github.com/hackorum-dev/postgres.git

then, for this patchset and every later one:

git fetch hackorum t52208_10 && git checkout t52208_10

Patchset v10 (message #10) is on t52208_10

Jump to latest
#1Nikolay Shaplov
dhyan@nataraj.su

Hi, All!

There is ongoing tendency in PostgreSQL to replace boolean reloptions that has
"on" and "off" state, with other option types that has "on", "off" and "use
defaults" states.

Some of these options are implemented as enum options. They are
"vacuum_index_cleanup" and gist's "buffering"

and one recently converted option "vacuum_truncate" that uses extra
"vacuum_truncate_set" flag to indicate it's unset state.

This patch introduce trenary reloptions type, that replaces both
implementation with separate data-type that behaves in the same way as bool
type does, but has one extra state that indicate that option have not been set
to "on" or "off" state.

This third state, I call it "unset" state, can either be only reached by not
setting "true" or "false" value to an option, as it is done in vacuum_truncate
option. Or option designer can assign this third state a custom name, so user
can explicitly set option to the "third" slate. As it is done in
`vacuum_index_cleanup` and gist's `buffering` option, using "auto" keyword.

This patch changes implementation of `vacuum_truncate`, `vacuum_index_cleanup`
and gist's `buffering` to trinary options. This make code more neat and
consistent. I'd suggest to commit it to the master branch.

Possible flaws and drawbacks:

1. I've added trenary enum type to c.h. It might be a bit too global, but I
did not find any better place for it, since it is needed globally and it is
kind of similar to boolean. If you know better place, please speak.

2. `vacuum_index_cleanup` and gist's `buffering` will now accepts all possible
"true" and "false" aliases as in boolean type, the way they did not do it
before. Like "1" or "FAL". I see no great harm in it, but it is still
behaviour change.

3. Error messages for `vacuum_index_cleanup` and gist's `buffering` are now
less informative. It is not right, but I do not see right now a way to improve
that. May be it is a price to pay for code consistency. If you have any idea
how to fix it, please speak.

As for the rest, other behavior should not be changed.

I've added as many tests as I can. local_reloption support is also
implemented.

I've split patch into four part so it can be read and reviewed step by step:
1. Tests that ensures old behaviour
2. Trenary option for `vacuum_truncate` reloption
3. Add "unset_alias" feature to implement "auto" alias for
`vacuum_index_cleanup` and gist's `buffering`
4. More tests

But I guess they should be commit as a single commit.

--
Nikolay Shaplov aka Nataraj
Fuzzing Engineer at Postgres Professional
Matrix IM: @dhyan:nataraj.su

Attachments:

v1-0001-Add-trenary-reloption-type.patchtext/x-patch; charset=unicode-2-0-utf-8; name=v1-0001-Add-trenary-reloption-type.patchDownload+57-1
v1-0002-Introduce-trenary-reloptions.patchtext/x-patch; charset=unicode-2-0-utf-8; name=v1-0002-Introduce-trenary-reloptions.patchDownload+135-44
v1-0003-Add-alias-to-be-used-as-unset-state.patchtext/x-patch; charset=unicode-2-0-utf-8; name=v1-0003-Add-alias-to-be-used-as-unset-state.patchDownload+54-83
v1-0004-Extra-tests.patchtext/x-patch; charset=unicode-2-0-utf-8; name=v1-0004-Extra-tests.patchDownload+81-14
#2Nikolay Shaplov
dhyan@nataraj.su
In reply to: Nikolay Shaplov (#1)
Re: [PATCH] ternary reloption type

My English is bad :-(

It is either trinary, or ternary, but not what I've written in previous
message.

Thanks to Timur for pointing to this issue.

Here goes a new version of the patch with proper naming for an new option
type.

--
Nikolay Shaplov aka Nataraj
Fuzzing Engineer at Postgres Professional
Matrix IM: @dhyan:nataraj.su

Attachments:

v2-0001-Add-ternary-reloption-type.patchtext/x-patch; charset=unicode-2-0-utf-8; name=v2-0001-Add-ternary-reloption-type.patchDownload+57-1
v2-0002-Introduce-ternary-reloptions.patchtext/x-patch; charset=unicode-2-0-utf-8; name=v2-0002-Introduce-ternary-reloptions.patchDownload+135-44
v2-0003-Add-alias-to-be-used-as-unset-state.patchtext/x-patch; charset=unicode-2-0-utf-8; name=v2-0003-Add-alias-to-be-used-as-unset-state.patchDownload+54-83
v2-0004-Extra-tests.patchtext/x-patch; charset=unicode-2-0-utf-8; name=v2-0004-Extra-tests.patchDownload+81-14
#3Timur Magomedov
t.magomedov@postgrespro.ru
In reply to: Nikolay Shaplov (#2)
Re: [PATCH] ternary reloption type

Hello Nikolay!

Found a typo in reloptions.h, treaed -> treated.

Can ternary enum be added in a separate header file, say,
src/include/ternary.h instead of adding it to c.h? I'm just not sure if
c.h is it the right place for relation-options-specific code.
Of course, I can be wrong.

--
Regards,
Timur Magomedov

#4Nikolay Shaplov
dhyan@nataraj.su
In reply to: Timur Magomedov (#3)
Re: [PATCH] ternary reloption type

В письме от пятница, 12 сентября 2025 г. 16:46:19 MSK пользователь Timur
Magomedov написал:

Hello Nikolay!

Found a typo in reloptions.h, treaed -> treated.

Oups. Fixed that in the attached version.

Can ternary enum be added in a separate header file, say,
src/include/ternary.h instead of adding it to c.h? I'm just not sure if
c.h is it the right place for relation-options-specific code.
Of course, I can be wrong.

I am not sure either. But my guess is that spamming into c.h is lesser crime
then adding another useless header file.

Moreover, ternary value is not relation-options-specific, it is actually
relation specific, if you think about it thoroughly. Relation code uses it, and
there is no way to avoid that.

Are there any other notions about the code?

I tried to make thongs more neat and more consistent here. Did I succeed?

--
Nikolay Shaplov aka Nataraj
Fuzzing Engineer at Postgres Professional
Matrix IM: @dhyan:nataraj.su

Attachments:

v2a-0001-Add-ternary-reloption-type.patchtext/x-patch; charset=unicode-2-0-utf-8; name=v2a-0001-Add-ternary-reloption-type.patchDownload+57-1
v2a-0002-Introduce-ternary-reloptions.patchtext/x-patch; charset=unicode-2-0-utf-8; name=v2a-0002-Introduce-ternary-reloptions.patchDownload+135-44
v2a-0003-Add-alias-to-be-used-as-unset-state.patchtext/x-patch; charset=unicode-2-0-utf-8; name=v2a-0003-Add-alias-to-be-used-as-unset-state.patchDownload+54-83
v2a-0004-Extra-tests.patchtext/x-patch; charset=unicode-2-0-utf-8; name=v2a-0004-Extra-tests.patchDownload+81-14
#5Alvaro Herrera
alvherre@2ndquadrant.com
In reply to: Nikolay Shaplov (#4)
Re: [PATCH] ternary reloption type

I took a quick look at 0001+0002 and I think it's quite reasonable.
Here it is again with some minor fixups. (I'm omitting the further
patches for now, we can rebase them later.)

I'm CCing Nathan as committer of the vacuum_truncate_set stuff which
Nikolay so strongly disliked. Any objections to going with this
approach?

Thanks,

(Please note that Gmail is sabotaging my kurilemu.de domain, so there's
significant delay in my emails to the list from that address. I guess
I'm lucky that Nikolay decided to CC my old address in this thread.)

--
Álvaro Herrera PostgreSQL Developer — https://www.EnterpriseDB.com/
"The problem with the future is that it keeps turning into the present"
(Hobbes)

Attachments:

v3-0001-Introduce-ternary-reloptions.patchtext/x-diff; charset=utf-8Download+202-44
#6Nathan Bossart
nathandbossart@gmail.com
In reply to: Alvaro Herrera (#5)
Re: [PATCH] ternary reloption type

On Fri, Jan 16, 2026 at 04:14:52PM +0100, Álvaro Herrera wrote:

I'm CCing Nathan as committer of the vacuum_truncate_set stuff which
Nikolay so strongly disliked. Any objections to going with this
approach?

Looks generally reasonable.

This could also be used for other options such as `vacuum_index_cleanup`
and `buffering`, but lets get the scaffolding in first.

Part of me wonders if we should just modify the Boolean relopt
implementation instead of using ternary only when needed.

+				parsed = parse_bool(value, &b);
+				option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+				if (validate && !parsed)
+					ereport(ERROR,
+							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+							 errmsg("invalid value for ternary option \"%s\": %s",
+									option->gen->name, value)));

Shouldn't this say "invalid value for boolean option"? IIUC the intent is
for ternary to be exactly like bool, except it defaults to an "unset" value
that can't be chosen by the user. In that sense, I think "ternary" is kind
of a misnomer, but I wouldn't count this as an objection.

--
nathan

#7Nikolay Shaplov
dhyan@nataraj.su
In reply to: Alvaro Herrera (#5)
Re: [PATCH] ternary reloption type

On 16.01.2026 18:14, Álvaro Herrera wrote:

I took a quick look at 0001+0002 and I think it's quite reasonable.
Here it is again with some minor fixups.

Good. I like ternary -> pg_ternary change. That is reasonable. And
postgres.h is better place for it then c.h.

(I'm omitting the further
patches for now, we can rebase them later.)

I've rebased the rest of patches, and the whole patchset is in the
attachment.

I'm CCing Nathan as committer of the vacuum_truncate_set stuff which
Nikolay so strongly disliked. Any objections to going with this
approach?

Ok, will quote him and reply below in the this letter.

This could also be used for other options such as `vacuum_index_cleanup`
and `buffering`, but lets get the scaffolding in first.

Part of me wonders if we should just modify the Boolean relopt
implementation instead of using ternary only when needed.

Ternary option, with it's third optional option is quite complex.

I'd rather not bother postgres developer with this complexity if they
just want to add pure boolean option.

+				parsed = parse_bool(value, &b);
+				option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE;
+				if (validate && !parsed)
+					ereport(ERROR,
+							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+							 errmsg("invalid value for ternary option \"%s\": %s",
+									option->gen->name, value)));

Shouldn't this say "invalid value for boolean option"? IIUC the intent is
for ternary to be exactly like bool, except it defaults to an "unset" value
that can't be chosen by the user. In that sense, I think "ternary" is kind
of a misnomer, but I wouldn't count this as an objection.

If you look at buffering option for example, you will see it does not
behave as pure boolean option.
"auto" value can be explicitly set by user.

Error reporting is not strong part of my patch, I would agree with that.

What if we give two different error reports in two different cases:

Give boolean-like error report when option does not have explicit alias
for third option, and more verbose
error message, listing all available options in the case when third
option have explicit alias?

like

"invalid value for boolean option \"%s\": %s",

and

"invalid value for option \"%s\": %s",
"valid values are 'on', 'off' and '%s'"

we may actually say nothing about option being ternary here

Attachments:

v3a-0004-Extra-tests.patchtext/x-patch; charset=UTF-8; name=v3a-0004-Extra-tests.patchDownload+81-14
v3a-0003-Add-alias-to-be-used-as-unset-state.patchtext/x-patch; charset=UTF-8; name=v3a-0003-Add-alias-to-be-used-as-unset-state.patchDownload+57-88
v3a-0002-Introduce-ternary-reloptions.patchtext/x-patch; charset=UTF-8; name=v3a-0002-Introduce-ternary-reloptions.patchDownload+145-44
v3a-0001-Add-ternary-reloption-type.patchtext/x-patch; charset=UTF-8; name=v3a-0001-Add-ternary-reloption-type.patchDownload+57-1
#8Alvaro Herrera
alvherre@2ndquadrant.com
In reply to: Nikolay Shaplov (#7)
Re: [PATCH] ternary reloption type

On 2026-Jan-21, Nikolay Shaplov wrote:

On 16.01.2026 18:14, Álvaro Herrera wrote:

I took a quick look at 0001+0002 and I think it's quite reasonable.
Here it is again with some minor fixups.

Good. I like ternary -> pg_ternary change. That is reasonable. And
postgres.h is better place for it then c.h.

I further changed TERNARY_TRUE and so on to have a PG_ prefix also; it's
not impossible that there's userland code somewhere outside Postgres
that uses those symbol names, so let's avoid a collision.

I've rebased the rest of patches, and the whole patchset is in the
attachment.

I'm not a fan of how these commit messages are structured. You explain
the point of the whole patch series in the commit message for 0001, but
that one only adds some tests for the existing behavior. If I were to
commit that, it would make no sense in the overall Postgres commit
history. So I have squashed 0001 with 0002, and also with the fraction
of 0004 that includes tests for the feature in 0002. I think it's
strange to submit those tests in 0004, when the other half of the tests
are for the feature in 0003. I recommend to consider how you structure
your patch splits so that they would make sense in the Postgres commit
history assuming they are committed on separate days, and that there are
multiple other commits in between.

I do agree with Nathan that there seems to be little point in the
message saying this new type is different from Boolean. The user can
still say only "on" or "off". Even with your "alias" proposal, there
will only be a mechanism to let the system choose between those two
values, but it will still be one or the other. There's no provision to
have the system behave as if the user set the value to half.

Another thing I did is remove default_val for ternaries. As far as I
can see, it makes no sense. If your reloption defaults to either on or
off, then it's just a Boolean, right? It can no longer be unset,
because if you unset it, then it becomes the default.

Anyway, I have pushed the first part, after rewriting the commit
message. You can resubmit the rest after rebasing on the current tree.
I have also marked the commitfest item as committed. Please create a
new one for the next part.

Thanks,

--
Álvaro Herrera Breisgau, Deutschland — https://www.EnterpriseDB.com/
"El sentido de las cosas no viene de las cosas, sino de
las inteligencias que las aplican a sus problemas diarios
en busca del progreso." (Ernesto Hernández-Novich)

#9Nikolay Shaplov
dhyan@nataraj.su
In reply to: Alvaro Herrera (#8)
Re: [PATCH] ternary reloption type

On 21.01.2026 22:23, Álvaro Herrera wrote:

I've rebased the rest of patches, and the whole patchset is in the
attachment.

I'm not a fan of how these commit messages are structured. You explain
the point of the whole patch series in the commit message for 0001, but
that one only adds some tests for the existing behavior. If I were to
commit that, it would make no sense in the overall Postgres commit
history. So I have squashed 0001 with 0002, and also with the fraction
of 0004 that includes tests for the feature in 0002.

I guess that comes from my frustration with big reloptions patch. I've
been  looking
for a way to make iy more easy to read and understand. My thought was
that if
I split them into logical "layers" showing the  development of the
patch's idea, it
would be more easy to understand them as a whole. And they were intended
to be squashed before commit, may be I should state that more clearly.

I think it's
strange to submit those tests in 0004, when the other half of the tests
are for the feature in 0003.

Yeah, here you are right, I should add tests both to 0003 and 0004. I
did not think
they can be committed separately

I recommend to consider how you structure
your patch splits so that they would make sense in the Postgres commit
history assuming they are committed on separate days, and that there are
multiple other commits in between.

Second part of ternary options patch is consist of one piece, so now it
is not a problem,
and as for big reloptions patch, I think we can discuss it later. I
doubt it is readable
when it is provided in single piece.

I do agree with Nathan that there seems to be little point in the
message saying this new type is different from Boolean. The user can
still say only "on" or "off". Even with your "alias" proposal, there
will only be a mechanism to let the system choose between those two
values, but it will still be one or the other. There's no provision to
have the system behave as if the user set the value to half.

For ternary options with explicit "third" value, I added another error
message, it says nothing
about option type, it just lists possible values. This can be good solution.

Another thing I did is remove default_val for ternaries. As far as I
can see, it makes no sense. If your reloption defaults to either on or
off, then it's just a Boolean, right? It can no longer be unset,
because if you unset it, then it becomes the default.

For the part you've committed that is correct. But with explicit "third"
option, you can't tell
for sure which value is default. Like:
prefer_XXXX_optimization: yes/no/never
It can be implemented as ternary option, but default value here can be
"yes".
I would not try to predict what behavior option developer will need, and
try to provide all
possibilities.
That's why I put default value back. But I will not be much upset if you
still decide to remove it.
Or I can remove it myself if you insist. We can add it later when
someone runs into this "never" case.

Anyway, I have pushed the first part, after rewriting the commit
message.

Thanks! Now the world is better place, from my point of view.

You can resubmit the rest after rebasing on the current tree.
I have also marked the commitfest item as committed. Please create a
new one for the next part.

I've reworked second part of the patch, It is in the attachment, and I
am going to create new commitfest record for it. Thank you for your work.

Attachments:

v1a-0001-Convert-vacuum_index_cleanup-and-gist-s-bufferin.patchtext/x-patch; charset=UTF-8; name=v1a-0001-Convert-vacuum_index_cleanup-and-gist-s-bufferin.patchDownload+139-95
#10Nikolay Shaplov
dhyan@nataraj.su
In reply to: Nikolay Shaplov (#9)
Re: [PATCH] ternary reloption type

В письме от четверг, 5 февраля 2026 г. 16:08:59 Москва, стандартное время
пользователь Nikolay Shaplov написал:

Here hoes a rebased version of second part of ternary patch that changes
`vacuum_index_cleanup` and GiST's `buffering` reloptions to ternary type

--
Nikolay Shaplov aka Nataraj
Fuzzing Engineer at Postgres Professional
Matrix IM: @dhyan:nataraj.su

Attachments:

t52208_10
v2-0001-Convert-vacuum_index_cleanup-and-GiST-s-buffering.patchtext/x-patch; charset=UTF-8; name=v2-0001-Convert-vacuum_index_cleanup-and-GiST-s-buffering.patchDownload+139-95
#11Peter Eisentraut
peter_e@gmx.net
In reply to: Alvaro Herrera (#8)
Re: [PATCH] ternary reloption type

On 21.01.26 20:23, Álvaro Herrera wrote:

On 2026-Jan-21, Nikolay Shaplov wrote:

On 16.01.2026 18:14, Álvaro Herrera wrote:

I took a quick look at 0001+0002 and I think it's quite reasonable.
Here it is again with some minor fixups.

Good. I like ternary -> pg_ternary change. That is reasonable. And
postgres.h is better place for it then c.h.

I further changed TERNARY_TRUE and so on to have a PG_ prefix also; it's
not impossible that there's userland code somewhere outside Postgres
that uses those symbol names, so let's avoid a collision.

I don't like that pg_ternary was added to postgres.h.

There are, depending on how you count, a few to many other ternary types
used throughout the tree, and it's not clear why this one should be the
standard one now. At least if so that should have involved some
discussion and analysis on the other ones. There are also some
tradeoffs about how this type should be designed. This particular one
uses 0 and 1 for false and true, and -1 for unset. Others use 0 for
unset and other values for false and true. Maybe this choice is useful
for this particular use, but we shouldn't impose it on everyone.

Independent of that, I don't understand why this was put into postgres.h
instead of c.h. It's not particular to backend code, as far as I can tell.

I think it would be better to rename this to something like
relopt_ternary and move it to access/reloptions.h.

If we want to consolidate all ternary types, that might be useful, but
it should be an explicit discussion.

#12Nikolay Shaplov
dhyan@nataraj.su
In reply to: Peter Eisentraut (#11)
Re: [PATCH] ternary reloption type

В письме от понедельник, 17 августа 2026 г. 16:03:12 Москва, стандартное время
пользователь Peter Eisentraut написал:

I don't like that pg_ternary was added to postgres.h.

That's understandable.

There are, depending on how you count, a few to many other ternary types
used throughout the tree, and it's not clear why this one should be the
standard one now. At least if so that should have involved some
discussion and analysis on the other ones. There are also some
tradeoffs about how this type should be designed. This particular one
uses 0 and 1 for false and true, and -1 for unset. Others use 0 for
unset and other values for false and true. Maybe this choice is useful
for this particular use, but we shouldn't impose it on everyone.

The idea was that from the reloptions point of view, ternary is a boolean with
one extra possibility. This comes about purely historically, because the
current and future ternary options are born from boolean options, so it's
convenient to keep the values that encode explicit 'yes' and 'no', so that the
corresponding fields in the database don't have to be updated when switching
from boolean to ternary. With this encoding, everything will keep working the
way it did without any pg_catalog update.

As for the 'third' value, using an enum seemed reasonable in this case, and
then you have to pick one specific value. If 0 and 1 are already taken, then -1
seems like the logical option.

When developing this patch, I wasn't aware of the existence of other ternary-
logic implementations in Postgres. I'm not against bringing these
implementations to a common style. But in the case of reloptions, we're
constrained by the fact that the data is already stored on disk and it's
better not to change it. If the other ternary values are used only in memory,
then it might be right to bring them to the same data type as the one used in
options. If you share a list of the other places where ternary logic is also
used, we'll all have a chance to look at it and assess how justified bringing
them to a common style would be.

Independent of that, I don't understand why this was put into postgres.h
instead of c.h. It's not particular to backend code, as far as I can tell.

I think it would be better to rename this to something like
relopt_ternary and move it to access/reloptions.h.

If it were up to me, I'd keep the definition of pg_ternary in access/
reloptions.h and not interfere with the core Postgres code. Unfortunately,
though, one of the ternary options value is located in the StdRdOptions struct
defined in include/utils/rel.h, so the pg_ternary type has to be defined in some
very global place. Which one exactly is debatable. In the original version of
the patch I put it in c.h. When committing, Álvaro moved it to postgres.h. I
concluded that Álvaro knows better where it should be. I don't have an opinion
of my own on this question — the main thing for me is that pg_ternary be
defined in a header file that can be included in utils/rel.h.

I guess some logic behind it might be like this: We using pg_ternary name, not
just ternary, because some other library header might also want to define
ternary. And since this type has pg_ suffix postgres.h seems to be better place
to store it, than c.h. pg_ means it is related to postgres. Things from c.h
are not postgres related.

If we want to consolidate all ternary types, that might be useful, but
it should be an explicit discussion.

I think we want. Me at least. Let's discuss it.

--
Nikolay Shaplov aka Nataraj
Fuzzing Engineer at Postgres Professional
Matrix IM: @dhyan:nataraj.su

#13Alvaro Herrera
alvherre@2ndquadrant.com
In reply to: Peter Eisentraut (#11)
Re: [PATCH] ternary reloption type

On 2026-Aug-17, Peter Eisentraut wrote:

There are, depending on how you count, a few to many other ternary types
used throughout the tree, and it's not clear why this one should be the
standard one now.

I asked Claude which ternaries we have. The response listed three, and
it started with:

pg_ternary — src/include/postgres.h
The canonical/general-purpose one. Values: PG_TERNARY_FALSE (0),
PG_TERNARY_TRUE (1), PG_TERNARY_UNSET (-1). Comment explicitly describes
it as a boolean with an extra "unset" value.

It's already considered the canonical one! That's a great start.

It then said
trivalue — src/bin/pg_dump/pg_backup.h
Used by pg_dump / client tools for command-line options. Values:
TRI_DEFAULT, TRI_NO, TRI_YES.

PGTernaryBool — src/interfaces/libpq/libpq-int.h (and an identical
copy in src/interfaces/libpq-oauth/oauth-utils.h)
A libpq internal "boolean plus not-known" for GUCs it may have to
fetch. Values: PG_BOOL_UNKNOWN (0), PG_BOOL_YES, PG_BOOL_NO.

That's the complete list it produced.

At least if so that should have involved some discussion
and analysis on the other ones. There are also some tradeoffs about how
this type should be designed. This particular one uses 0 and 1 for false
and true, and -1 for unset. Others use 0 for unset and other values for
false and true. Maybe this choice is useful for this particular use, but we
shouldn't impose it on everyone.

I think you're talking about the libpq one (PGTernaryBool), which dates
back to commit ee28cacf619f and was defined in libpq-int.h.

Independent of that, I don't understand why this was put into postgres.h
instead of c.h. It's not particular to backend code, as far as I can tell.

Right, it's not. It felt a bit out of place in c.h to me, and I didn't
see the argument for exposing it wider than postgres.h, but at the same
time it seemed to me that a notion this common can perfectly well use a
single central definition rather than have each module define the same
thing.

We have a handful of enums all called "trivalue" in various clients
programs, with the same definitions, and that doesn't seem great to me --
quite the opposite in fact. If we move pg_ternary to c.h and add
aliases TRI_YES / NO / DEFAULT, then we can remove the repetitive enum
typedefs and we'd probably be in a better position.

I think it would be better to rename this to something like relopt_ternary
and move it to access/reloptions.h.

I'm not sure what we gain from doing that. If there's generalized
opposition to having it in postgres.h, I'm open to renaming it as
suggested and moving it there.

If we want to consolidate all ternary types, that might be useful, but it
should be an explicit discussion.

The others I found were:

/*
* Represents whether a header line must match the actual names
* (which implies "true"), and whether it should be present.
*/
#define COPY_HEADER_MATCH -1
#define COPY_HEADER_FALSE 0
#define COPY_HEADER_TRUE 1

and

#define GIN_FALSE 0 /* item is not present / does not match */
#define GIN_TRUE 1 /* item is present / matches */
#define GIN_MAYBE 2 /* don't know if item is present / don't know
* if matches */

and it didn't seem that they had semantics similar enough to make them
use the new enum.

--
Álvaro Herrera Breisgau, Deutschland — https://www.EnterpriseDB.com/
"Sallah, I said NO camels! That's FIVE camels; can't you count?"
(Indiana Jones)