Received: from malur.postgresql.org ([217.196.149.56]) by arkaria.postgresql.org with esmtps (TLS1.3) tls TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384 (Exim 4.96) (envelope-from ) id 1wP3fs-000NYj-2w for pgsql-hackers@arkaria.postgresql.org; Mon, 18 May 2026 19:30:49 +0000 Received: from localhost ([127.0.0.1] helo=malur.postgresql.org) by malur.postgresql.org with esmtp (Exim 4.96) (envelope-from ) id 1wP3fp-002zQt-2w for pgsql-hackers@arkaria.postgresql.org; Mon, 18 May 2026 19:30:46 +0000 Received: from makus.postgresql.org ([2001:4800:3e1:1::229]) by malur.postgresql.org with esmtps (TLS1.3) tls TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384 (Exim 4.96) (envelope-from ) id 1wP3fp-002zQW-0W for pgsql-hackers@lists.postgresql.org; Mon, 18 May 2026 19:30:46 +0000 Received: from mail-lf1-x129.google.com ([2a00:1450:4864:20::129]) by makus.postgresql.org with esmtps (TLS1.3) tls TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256 (Exim 4.98.2) (envelope-from ) id 1wP3fl-00000000DVp-3onB for pgsql-hackers@lists.postgresql.org; Mon, 18 May 2026 19:30:44 +0000 Received: by mail-lf1-x129.google.com with SMTP id 2adb3069b0e04-5a8f9841616so2372189e87.0 for ; Mon, 18 May 2026 12:30:42 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20251104; t=1779132640; x=1779737440; darn=lists.postgresql.org; h=references:to:cc:in-reply-to:date:subject:mime-version:message-id :from:from:to:cc:subject:date:message-id:reply-to; bh=jdJzCjjdOOPEEz3BpaQQ57xxJe4qDUk9qcSQREh0r4w=; b=R4pF6VCxim5L08Z/k4aMdLqWaojqN5PV+9fAW45rBQYNNDeu9i2k9ZYpRVxym/x7XN wiWR9P3jLrXVe9Fll3+sNQppMyw1fdmv7+BhQ25REM0dXjzxpnUV9gRfWb3fN1uTMxph U2OWUzm0usFzaF49SQz1I/tdVJ9XkMwBgaPugWPf66MIS8m7K+3010azE9qszzxKFbJ+ g3USAY7+cjJzxeXwxzkvlKdn/B/8gR8fjC7NCrUPjZoRCyMn10vCs0+kkIfvmKD5TmaO s2oGkhoL/yHtn3ZNlcPQRl9xktm4xk4objmCPahf3MnRkaj2Hu2bPB6689nZ9E5z1xOK 1gDQ== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20251104; t=1779132640; x=1779737440; h=references:to:cc:in-reply-to:date:subject:mime-version:message-id :from:x-gm-gg:x-gm-message-state:from:to:cc:subject:date:message-id :reply-to; bh=jdJzCjjdOOPEEz3BpaQQ57xxJe4qDUk9qcSQREh0r4w=; b=Tz4c+gn1c51V8oBpVbqO2VgHGjuwntazilyYq0rWnf0uv0RfdA0pW44ijBgfe0jb6p eDSVx/TuHnyF7qBBoN1d7xSWp3Tj+o/FFjVCcKkwk0snvXBHWcjrFt6Faejnk1gIuN8y b7NK/ssA/T1kE97KJiwb7TwnhB0O2o0pQ7fav7eBpjPWCfqzHaXTpuxXiDdiHbt7Ukos 4a9Goh8F0QsOJbDc7jD+pU/EbUBIoTBenlg1FLLgbZGY1CAlBXYkhMLp1OO91zCYEGpk Vltai4E8Zjc6r8gKO52Aa4MJEdoU0m1WhcrbLDjJPG4H+l7PsqOtVHhXzbbvbxro5uqQ 0gGw== X-Gm-Message-State: AOJu0YxVwrQrDshpLYAxYJhSPBd4y0d40KIOx7ZkHDfNogCm6uV/1DHL 5fB0fgKKkhJhTjtZ6izAvK/PrNJN/Wrzkz1b6P2Wa662o4ZQttgKVfNlu0mmREJTZ4UUag== X-Gm-Gg: Acq92OFOOAuK/GUhSnJmJoTKqst1mbGgTD9TQby/uUnh8tZqThVRWaZu8VPNyVI2HIA F+TtZMtzjF0N8Wo7NY6O5MLy61lm8hTf6qCDOIYRlvBXBrBcvgaChTwxJilOY/suvPE6Y3LajCz Qt3AYM+AQGD+Y/qZpxmPB1zBve/EZsPKvK+tGhmZNNkd0ul6miskn7mkh0/A5sGj3OsfuwsW3jc KZHLePQ5jd8Vj0N21Otm/9xJVcfildYLvV/4BHEUZ2ptjS7WegaB2GwRehzXDS6tFgwXbTFojz6 23jX17rFGLZuonSy6dL1B4csGOwrgp4+QCaUn05wb1jDK5vvmvLTq0oMDqVx7eJd3A+l6830nbv 0ldzjcvXMqGpF8heUQyWHvd2UPiLdEroNEuTy6Eu2oyRB1uqHbcbSx+QfETlz1fMkyNpf8CSFYZ ZBS/1HZsOoErZJLMD+v7lpepGFip8Cryn5Qd9kK3tG4w4Id608sMg= X-Received: by 2002:a05:6512:4026:b0:5a8:735f:aeba with SMTP id 2adb3069b0e04-5aa0e732188mr5251250e87.23.1779132639110; Mon, 18 May 2026 12:30:39 -0700 (PDT) Received: from smtpclient.apple ([91.236.81.140]) by smtp.gmail.com with ESMTPSA id 2adb3069b0e04-5a90f10c89bsm3585614e87.13.2026.05.18.12.30.36 (version=TLS1_2 cipher=ECDHE-ECDSA-AES128-GCM-SHA256 bits=128/128); Mon, 18 May 2026 12:30:36 -0700 (PDT) From: Andrey Rachitskiy Message-Id: <5673948B-91DF-422A-A5DC-DCD7E1DA87DA@gmail.com> Content-Type: multipart/alternative; boundary="Apple-Mail=_18AE1723-4D48-494F-8B4C-6DAD13E91C24" Mime-Version: 1.0 (Mac OS X Mail 16.0 \(3864.600.51.1.1\)) Subject: Re: [PATCH] ternary reloption type Date: Tue, 19 May 2026 00:30:24 +0500 In-Reply-To: <6925379.4vTCxPXJkl@thinkpad-pgpro> Cc: dhyan@nataraj.su, alvherre@alvh.no-ip.org, chris.travers@gmail.com, nathandbossart@gmail.com To: pgsql-hackers@lists.postgresql.org References: <6925379.4vTCxPXJkl@thinkpad-pgpro> X-Mailer: Apple Mail (2.3864.600.51.1.1) List-Id: List-Help: List-Subscribe: List-Post: List-Owner: List-Archive: Archived-At: Precedence: bulk --Apple-Mail=_18AE1723-4D48-494F-8B4C-6DAD13E91C24 Content-Transfer-Encoding: quoted-printable Content-Type: text/plain; charset=utf-8 Hi, Nikolay! Here is a brief review and suggestions for the patch. ``` --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -98,8 +98,8 @@ typedef struct relopt_bool typedef struct relopt_ternary { relopt_gen gen; - const char *unset_alias; /* word that will be treated as = unset value */ - int default_val; + const char *unset_alias; /* word treated as unset, or = NULL */ + pg_ternary default_val; } relopt_ternary; typedef struct relopt_int ``` The v2 patch added default_val but typed it as int. The field holds = PG_TERNARY_TRUE/FALSE/UNSET, so it should use pg_ternary, same as = ternary_val in relopt_value and the add_ternary_reloption() parameters. ``` --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -915,7 +915,8 @@ add_local_bool_reloption(local_relopts *relopts, = const char *name, */ static relopt_ternary * init_ternary_reloption(uint32 kinds, const char *name, const char = *desc, - pg_ternary default_val, const char* = unset_alias, LOCKMODE lockmode) + pg_ternary default_val, const = char *unset_alias, + LOCKMODE lockmode) { relopt_ternary *newoption; @@ -933,7 +934,8 @@ init_ternary_reloption(uint32 kinds, const char = *name, const char *desc, */ void add_ternary_reloption(uint32 kinds, const char *name, const char *desc, - pg_ternary default_val, const char* unset_alias, = LOCKMODE lockmode) + pg_ternary default_val, const = char *unset_alias, + LOCKMODE lockmode) { relopt_ternary *newoption; @@ -952,7 +954,7 @@ add_ternary_reloption(uint32 kinds, const char = *name, const char *desc, void add_local_ternary_reloption(local_relopts *relopts, const char *name, const char = *desc, pg_ternary default_val, - const char* = unset_alias, int offset) + const char = *unset_alias, int offset) { relopt_ternary *newoption; --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -191,8 +191,8 @@ extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(uint32 kinds, const char *name, const = char *desc, bool = default_val, LOCKMODE lockmode); extern void add_ternary_reloption(uint32 kinds, const char *name, - const char *desc, pg_ternary = default_val, - const char* unset_alias, = LOCKMODE lockmode); + const = char *desc, pg_ternary default_val, + const = char *unset_alias, LOCKMODE lockmode); extern void add_int_reloption(uint32 kinds, const char *name, const = char *desc, int = default_val, int min_val, int max_val, LOCKMODE = lockmode); @@ -213,9 +213,9 @@ extern void add_local_bool_reloption(local_relopts = *relopts, const char *name, = const char *desc, bool default_val, = int offset); extern void add_local_ternary_reloption(local_relopts *relopts, - const = char *name, const char *desc, - = pg_ternary default_val, const char* unset_alias, - int = offset); + = const char *name, const char *desc, + = pg_ternary default_val, + = const char *unset_alias, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char = *name, = const char *desc, int default_val, = int min_val, int max_val, int offset); ``` Formatting only (const char * and prototype alignment), no semantic = change. ``` --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -1723,10 +1725,10 @@ parse_one_reloption(relopt_value *option, char = *text_str, int text_len, if (validate && !parsed) ereport(ERROR, = (errcode(ERRCODE_INVALID_PARAMETER_VALUE), - errmsg("invalid = value for option \"%s\": %s", + errmsg("invalid = value for option \"%s\": %s", = option->gen->name, value), - errdetail("Valid = values are \"on\", \"off\", and \"%s\".", - = opt->unset_alias))); + = errdetail("Valid values are \"on\", \"off\", \"true\", \"false\", = \"yes\", \"no\", \"1\", \"0\", and \"%s\".", + = opt->unset_alias))); } break; case RELOPT_TYPE_INT: --- a/src/test/modules/dummy_index_am/expected/reloptions.out +++ b/src/test/modules/dummy_index_am/expected/reloptions.out @@ -123,7 +123,7 @@ ERROR: invalid value for boolean option = "option_ternary_1": do_not_know_yet ALTER INDEX dummy_test_idx SET (option_ternary_2 =3D = 'do_not_know_yet'); -- ok ALTER INDEX dummy_test_idx SET (option_ternary_2 =3D 'illegal_value'); = -- error ERROR: invalid value for option "option_ternary_2": illegal_value -DETAIL: Valid values are "on", "off", and "do_not_know_yet". +DETAIL: Valid values are "on", "off", "true", "false", "yes", "no", = "1", "0", and "do_not_know_yet". SELECT unnest(reloptions) FROM pg_class WHERE relname =3D = 'dummy_test_idx'; unnest ---------------------------------- ``` Ternary values are parsed with parse_bool() and then unset_alias, so a = rejection should tell the user the full set of valid spellings. The = previous enum-based vacuum_index_cleanup error effectively did that; we = restore the same level of detail for the ternary path. ``` @@ -1901,7 +1903,8 @@ fillRelOptions(void *rdopts, Size basesize, break; case RELOPT_TYPE_TERNARY: *(pg_ternary *) itempos = =3D options[i].isset ? - = options[i].ternary_val : PG_TERNARY_UNSET; + = options[i].ternary_val : + ((relopt_ternary = *) options[i].gen)->default_val; break; case RELOPT_TYPE_INT: *(int *) itempos =3D = options[i].isset ? ``` parseRelOptions() returns every option of a kind, with isset=3Dfalse = when it is not in pg_class.reloptions. For bool/int/enum we already = apply default_val in that case; ternary was still hardcoding = PG_TERNARY_UNSET, so default_val on relopt_ternary was unused. Built-in ternaries (vacuum_truncate, vacuum_index_cleanup, buffering) = all have default_val =3D=3D PG_TERNARY_UNSET, so behavior for them is = unchanged. This matters for AMs that call add_ternary_reloption() with = another default (dummy_index_am option_ternary_2). ``` --- a/src/backend/access/gist/gistutil.c +++ b/src/backend/access/gist/gistutil.c @@ -913,7 +913,8 @@ gistoptions(Datum reloptions, bool validate) { static const relopt_parse_elt tab[] =3D { {"fillfactor", RELOPT_TYPE_INT, offsetof(GiSTOptions, = fillfactor)}, - {"buffering", RELOPT_TYPE_ENUM, offsetof(GiSTOptions, = buffering_mode)} + {"buffering", RELOPT_TYPE_TERNARY, + offsetof(GiSTOptions, buffering_mode)} }; ``` buffering is registered globally as a ternary reloption; the local = relopt_parse_elt table still said ENUM after v2. fillRelOptions() uses = options[i].gen->type from the global registry, so this was not a runtime = bug, but the table was wrong and confusing for anyone reading the GiST = options path. ``` --- a/src/test/regress/expected/reloptions.out +++ b/src/test/regress/expected/reloptions.out @@ -134,6 +134,23 @@ SELECT reloptions FROM pg_class WHERE oid =3D = 'reloptions_test'::regclass; {vacuum_index_cleanup=3Dauto} (1 row) +-- boolean synonyms are accepted +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=3Dtrue); +SELECT reloptions FROM pg_class WHERE oid =3D = 'reloptions_test'::regclass; + reloptions +----------------------------- + {vacuum_index_cleanup=3Dtrue} +(1 row) + +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=3Dfalse); +SELECT reloptions FROM pg_class WHERE oid =3D = 'reloptions_test'::regclass; + reloptions +------------------------------ + {vacuum_index_cleanup=3Dfalse} +(1 row) + --- a/src/test/regress/sql/reloptions.sql +++ b/src/test/regress/sql/reloptions.sql @@ -80,6 +80,14 @@ DROP TABLE reloptions_test; CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=3Dauto); SELECT reloptions FROM pg_class WHERE oid =3D = 'reloptions_test'::regclass; +-- boolean synonyms are accepted +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=3Dtrue); +SELECT reloptions FROM pg_class WHERE oid =3D = 'reloptions_test'::regclass; +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=3Dfalse); +SELECT reloptions FROM pg_class WHERE oid =3D = 'reloptions_test'::regclass; + -- Test vacuum_truncate option DROP TABLE reloptions_test; ``` After moving vacuum_index_cleanup from enum to ternary, we still accept = true/false via parse_bool(). These tests lock that in and show the value = is stored in pg_class.reloptions as given (not normalized to on/off). ``` --- a/src/test/regress/expected/gist.out +++ b/src/test/regress/expected/gist.out @@ -9,11 +9,12 @@ create index gist_pointidx on gist_point_tbl using = gist(p); create index gist_pointidx2 on gist_point_tbl using gist(p) with = (buffering =3D on, fillfactor=3D50); create index gist_pointidx3 on gist_point_tbl using gist(p) with = (buffering =3D off); create index gist_pointidx4 on gist_point_tbl using gist(p) with = (buffering =3D auto); -drop index gist_pointidx2, gist_pointidx3, gist_pointidx4; +create index gist_pointidx4b on gist_point_tbl using gist(p) with = (buffering =3D true); +drop index gist_pointidx2, gist_pointidx3, gist_pointidx4, = gist_pointidx4b; -- Make sure bad values are refused create index gist_pointidx5 on gist_point_tbl using gist(p) with = (buffering =3D invalid_value); ERROR: invalid value for option "buffering": invalid_value -DETAIL: Valid values are "on", "off", and "auto". +DETAIL: Valid values are "on", "off", "true", "false", "yes", "no", = "1", "0", and "auto". create index gist_pointidx5 on gist_point_tbl using gist(p) with = (fillfactor=3D9); ERROR: value 9 out of bounds for option "fillfactor" DETAIL: Valid values are between "10" and "100". --- a/src/test/regress/sql/gist.sql +++ b/src/test/regress/sql/gist.sql @@ -11,7 +11,8 @@ create index gist_pointidx on gist_point_tbl using = gist(p); create index gist_pointidx2 on gist_point_tbl using gist(p) with = (buffering =3D on, fillfactor=3D50); create index gist_pointidx3 on gist_point_tbl using gist(p) with = (buffering =3D off); create index gist_pointidx4 on gist_point_tbl using gist(p) with = (buffering =3D auto); -drop index gist_pointidx2, gist_pointidx3, gist_pointidx4; +create index gist_pointidx4b on gist_point_tbl using gist(p) with = (buffering =3D true); +drop index gist_pointidx2, gist_pointidx3, gist_pointidx4, = gist_pointidx4b; ``` Same idea for GiST buffering: true should work as a synonym for on. = Expected output updated for the new index and the extended DETAIL line = on invalid input. ------ Regards, Andrey Rachitskiy =EF=BF=BC > 18 =D0=BC=D0=B0=D1=8F 2026=E2=80=AF=D0=B3., =D0=B2 19:11, Nikolay = Shaplov =D0=BD=D0=B0=D0=BF=D0=B8=D1=81=D0=B0=D0=BB(=D0=B0= ): >=20 > --=20 > Nikolay Shaplov aka Nataraj > Fuzzing Engineer at Postgres Professional > Matrix IM: @dhyan:nataraj.su >=20 > =D0=9E=D1=82=D0=BF=D1=80=D0=B0=D0=B2=D0=B8=D1=82=D0=B5=D0=BB=D1=8C: = Nikolay Shaplov > =D0=A2=D0=B5=D0=BC=D0=B0: =D0=9E=D1=82=D0=B2=D0=B5=D1=82: [PATCH] = ternary reloption type > =D0=94=D0=B0=D1=82=D0=B0: 10 =D0=BC=D0=B0=D1=8F 2026=E2=80=AF=D0=B3. = =D0=B2 16:09:28 GMT+5 > =D0=9A=D0=BE=D0=BC=D1=83: =C3=81lvaro Herrera = , pgsql-hackers@lists.postgresql.org > =D0=9A=D0=BE=D0=BF=D0=B8=D1=8F: PostgreSQL Hackers = , Chris Travers = , Timur Magomedov , = Nathan Bossart , Nikolay Shaplov = >=20 >=20 > =D0=92 =D0=BF=D0=B8=D1=81=D1=8C=D0=BC=D0=B5 =D0=BE=D1=82 = =D1=87=D0=B5=D1=82=D0=B2=D0=B5=D1=80=D0=B3, 5 =D1=84=D0=B5=D0=B2=D1=80=D0=B0= =D0=BB=D1=8F 2026=E2=80=AF=D0=B3. 16:08:59 =D0=9C=D0=BE=D1=81=D0=BA=D0=B2=D0= =B0, =D1=81=D1=82=D0=B0=D0=BD=D0=B4=D0=B0=D1=80=D1=82=D0=BD=D0=BE=D0=B5 = =D0=B2=D1=80=D0=B5=D0=BC=D1=8F=20 > =D0=BF=D0=BE=D0=BB=D1=8C=D0=B7=D0=BE=D0=B2=D0=B0=D1=82=D0=B5=D0=BB=D1=8C= Nikolay Shaplov =D0=BD=D0=B0=D0=BF=D0=B8=D1=81=D0=B0=D0=BB: >=20 > Here hoes a rebased version of second part of ternary patch that = changes=20 > `vacuum_index_cleanup` and GiST's `buffering` reloptions to ternary = type >=20 > --=20 > Nikolay Shaplov aka Nataraj > Fuzzing Engineer at Postgres Professional > Matrix IM: @dhyan:nataraj.su =EF=BF=BC >=20 --Apple-Mail=_18AE1723-4D48-494F-8B4C-6DAD13E91C24 Content-Type: multipart/mixed; boundary="Apple-Mail=_C84369F9-DF22-42A5-9F38-900AB9291ED4" --Apple-Mail=_C84369F9-DF22-42A5-9F38-900AB9291ED4 Content-Transfer-Encoding: quoted-printable Content-Type: text/html; charset=us-ascii
Hi, Nikolay!
Here is = a brief review and suggestions for the = patch.

```
--- = a/src/include/access/reloptions.h
+++ = b/src/include/access/reloptions.h
@@ -98,8 +98,8 @@ typedef = struct relopt_bool
 typedef struct = relopt_ternary
 {
        = relopt_gen      gen;
-       = const char      *unset_alias; /* word that will be = treated as unset value */
-       int   =                   = default_val;
+       const char     =  *unset_alias;   /* word treated as unset, or NULL = */
+       pg_ternary     =  default_val;
 } = relopt_ternary;

 typedef struct = relopt_int
```
The v2 patch added default_val but = typed it as int. The field holds PG_TERNARY_TRUE/FALSE/UNSET, so it = should use pg_ternary, same as ternary_val in relopt_value and the = add_ternary_reloption() = parameters.


```
--- = a/src/backend/access/common/reloptions.c
+++ = b/src/backend/access/common/reloptions.c
@@ -915,7 +915,8 @@ = add_local_bool_reloption(local_relopts *relopts, const char = *name,
  */
 static relopt_ternary = *
 init_ternary_reloption(uint32 kinds, const char *name, = const char *desc,
-             =            pg_ternary default_val, const = char* unset_alias, LOCKMODE lockmode)
+       =                     =                pg_ternary = default_val, const char *unset_alias,
+       =                     =                LOCKMODE = lockmode)
 {
        = relopt_ternary *newoption;

@@ -933,7 +934,8 @@ = init_ternary_reloption(uint32 kinds, const char *name, const char = *desc,
  = */
 void
 add_ternary_reloption(uint32 = kinds, const char *name, const char *desc,
-     =             pg_ternary default_val, const = char* unset_alias,  LOCKMODE lockmode)
+     =                     =                 pg_ternary = default_val, const char *unset_alias,
+       =                     =               LOCKMODE = lockmode)
 {
        = relopt_ternary *newoption;

@@ -952,7 +954,7 @@ = add_ternary_reloption(uint32 kinds, const char *name, const char = *desc,
 void
 add_local_ternary_reloption(lo= cal_relopts *relopts, const char *name,
      =                     =                     =           const char *desc, pg_ternary = default_val,
-             =                     =                     =   const char* unset_alias, int offset)
+     =                     =                     =           const char *unset_alias, int = offset)
 {
        = relopt_ternary *newoption;

--- = a/src/include/access/reloptions.h
+++ = b/src/include/access/reloptions.h
@@ -191,8 +191,8 @@ extern = relopt_kind add_reloption_kind(void);
 extern void = add_bool_reloption(uint32 kinds, const char *name, const char = *desc,
                =                     =                     =    bool default_val, LOCKMODE = lockmode);
 extern void add_ternary_reloption(uint32 = kinds, const char *name,
-           =                     =         const char *desc, pg_ternary = default_val,
-             =                     =       const char* unset_alias, LOCKMODE = lockmode);
+               =                     =                     =           const char *desc, pg_ternary = default_val,
+             =                     =                     =             const char *unset_alias, = LOCKMODE lockmode);
 extern void add_int_reloption(uint32 = kinds, const char *name, const char *desc,
    =                     =                     =               int default_val, int = min_val, int max_val,
          =                     =                     =         LOCKMODE lockmode);
@@ -213,9 = +213,9 @@ extern void add_local_bool_reloption(local_relopts *relopts, = const char *name,
            =                     =                     =                     =  const char *desc, bool default_val,
      =                     =                     =                     =        int offset);
 extern void = add_local_ternary_reloption(local_relopts *relopts,
-   =                     =                     =                     = const char *name, const char *desc,
-       =                     =                     =                 pg_ternary = default_val, const char* unset_alias,
-       =                     =                     =                 int = offset);
+               =                     =                     =                     =     const char *name, const char *desc,
+   =                     =                     =                     =                 pg_ternary = default_val,
+             =                     =                     =                     =       const char *unset_alias, int = offset);
 extern void = add_local_int_reloption(local_relopts *relopts, const char = *name,
                =                     =                     =                 const char = *desc, int default_val,
          =                     =                     =                     =   int min_val, int max_val, int = offset);
```
Formatting only (const char * and = prototype alignment), no semantic = change.


```
--- = a/src/backend/access/common/reloptions.c
+++ = b/src/backend/access/common/reloptions.c
@@ -1723,10 +1725,10 = @@ parse_one_reloption(relopt_value *option, char *text_str, int = text_len,
              =                   if = (validate && !parsed)
        =                     =             = ereport(ERROR,
            =                     =                     =     (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
- =                     =                     =               errmsg("invalid value = for option \"%s\": %s",
+           =                     =                     =      errmsg("invalid value for option \"%s\": = %s",
                =                     =                     =                 = option->gen->name, value),
-         =                     =                     =       errdetail("Valid values are \"on\", \"off\", and = \"%s\".",
-               =                     =                     =                     =     opt->unset_alias)));
+       =                     =                     =          errdetail("Valid values are \"on\", = \"off\", \"true\", \"false\", \"yes\", \"no\", \"1\", \"0\", and = \"%s\".",
+               =                     =                     =                   =  opt->unset_alias)));
        =                 = }
                =         break;
        =         case = RELOPT_TYPE_INT:

--- = a/src/test/modules/dummy_index_am/expected/reloptions.out
+++ = b/src/test/modules/dummy_index_am/expected/reloptions.out
@@ = -123,7 +123,7 @@ ERROR:  invalid value for boolean option = "option_ternary_1": do_not_know_yet
 ALTER INDEX = dummy_test_idx SET (option_ternary_2 =3D 'do_not_know_yet'); -- = ok
 ALTER INDEX dummy_test_idx SET (option_ternary_2 =3D = 'illegal_value'); -- error
 ERROR:  invalid value = for option "option_ternary_2": illegal_value
-DETAIL: =  Valid values are "on", "off", and = "do_not_know_yet".
+DETAIL:  Valid values are "on", = "off", "true", "false", "yes", "no", "1", "0", and = "do_not_know_yet".
 SELECT unnest(reloptions) FROM = pg_class WHERE relname =3D 'dummy_test_idx';
    =           =  unnest
 ----------------------------------
= ```
Ternary values are parsed with parse_bool() and then = unset_alias, so a rejection should tell the user the full set of valid = spellings. The previous enum-based vacuum_index_cleanup error = effectively did that; we restore the same level of detail for the = ternary path.


```
@@ = -1901,7 +1903,8 @@ fillRelOptions(void *rdopts, Size = basesize,
              =                     =               break;
  =                     =                   case = RELOPT_TYPE_TERNARY:
            =                     =                 *(pg_ternary *) = itempos =3D options[i].isset ?
-         =                     =                     =       options[i].ternary_val : = PG_TERNARY_UNSET;
+             =                     =                     =   options[i].ternary_val :
+         =                     =                     =       ((relopt_ternary *) = options[i].gen)->default_val;
        =                     =                     = break;
                =                     =     case RELOPT_TYPE_INT:
      =                     =                     =   *(int *) itempos =3D options[i].isset = ?
```
parseRelOptions() returns every option of a = kind, with isset=3Dfalse when it is not in pg_class.reloptions. For = bool/int/enum we already apply default_val in that case; ternary was = still hardcoding PG_TERNARY_UNSET, so default_val on relopt_ternary was = unused.
Built-in ternaries (vacuum_truncate, = vacuum_index_cleanup, buffering) all have default_val =3D=3D = PG_TERNARY_UNSET, so behavior for them is unchanged. This matters for = AMs that call add_ternary_reloption() with another default = (dummy_index_am = option_ternary_2).


```
-= -- a/src/backend/access/gist/gistutil.c
+++ = b/src/backend/access/gist/gistutil.c
@@ -913,7 +913,8 @@ = gistoptions(Datum reloptions, bool = validate)
 {
        static = const relopt_parse_elt tab[] =3D {
        =         {"fillfactor", RELOPT_TYPE_INT, = offsetof(GiSTOptions, fillfactor)},
-       =         {"buffering", RELOPT_TYPE_ENUM, = offsetof(GiSTOptions, buffering_mode)}
+       =         {"buffering", = RELOPT_TYPE_TERNARY,
+           =     offsetof(GiSTOptions, buffering_mode)}
  =       };
```
buffering is registered = globally as a ternary reloption; the local relopt_parse_elt table still = said ENUM after v2. fillRelOptions() uses options[i].gen->type from = the global registry, so this was not a runtime bug, but the table was = wrong and confusing for anyone reading the GiST options = path.


```
--- = a/src/test/regress/expected/reloptions.out
+++ = b/src/test/regress/expected/reloptions.out
@@ -134,6 +134,23 = @@ SELECT reloptions FROM pg_class WHERE oid =3D = 'reloptions_test'::regclass;
  = {vacuum_index_cleanup=3Dauto}
 (1 = row)

+-- boolean synonyms are = accepted
+DROP TABLE reloptions_test;
+CREATE TABLE = reloptions_test(i INT) WITH = (vacuum_index_cleanup=3Dtrue);
+SELECT reloptions FROM = pg_class WHERE oid =3D 'reloptions_test'::regclass;
+   =       = reloptions
+-----------------------------
+ = {vacuum_index_cleanup=3Dtrue}
+(1 = row)
+
+DROP TABLE = reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH = (vacuum_index_cleanup=3Dfalse);
+SELECT reloptions FROM = pg_class WHERE oid =3D 'reloptions_test'::regclass;
+   =       =  reloptions
+------------------------------
+ = {vacuum_index_cleanup=3Dfalse}
+(1 = row)
+

--- = a/src/test/regress/sql/reloptions.sql
+++ = b/src/test/regress/sql/reloptions.sql
@@ -80,6 +80,14 @@ DROP = TABLE reloptions_test;
 CREATE TABLE reloptions_test(i = INT) WITH (vacuum_index_cleanup=3Dauto);
 SELECT = reloptions FROM pg_class WHERE oid =3D = 'reloptions_test'::regclass;

+-- boolean = synonyms are accepted
+DROP TABLE = reloptions_test;
+CREATE TABLE reloptions_test(i INT) WITH = (vacuum_index_cleanup=3Dtrue);
+SELECT reloptions FROM = pg_class WHERE oid =3D 'reloptions_test'::regclass;
+DROP = TABLE reloptions_test;
+CREATE TABLE reloptions_test(i INT) = WITH (vacuum_index_cleanup=3Dfalse);
+SELECT reloptions FROM = pg_class WHERE oid =3D = 'reloptions_test'::regclass;
+
 -- Test = vacuum_truncate option
 DROP TABLE = reloptions_test;
```
After moving = vacuum_index_cleanup from enum to ternary, we still accept true/false = via parse_bool(). These tests lock that in and show the value is stored = in pg_class.reloptions as given (not normalized to = on/off).


```
--- = a/src/test/regress/expected/gist.out
+++ = b/src/test/regress/expected/gist.out
@@ -9,11 +9,12 @@ create = index gist_pointidx on gist_point_tbl using = gist(p);
 create index gist_pointidx2 on gist_point_tbl = using gist(p) with (buffering =3D on, = fillfactor=3D50);
 create index gist_pointidx3 on = gist_point_tbl using gist(p) with (buffering =3D = off);
 create index gist_pointidx4 on gist_point_tbl = using gist(p) with (buffering =3D auto);
-drop index = gist_pointidx2, gist_pointidx3, gist_pointidx4;
+create index = gist_pointidx4b on gist_point_tbl using gist(p) with (buffering =3D = true);
+drop index gist_pointidx2, gist_pointidx3, = gist_pointidx4, gist_pointidx4b;
 -- Make sure bad values = are refused
 create index gist_pointidx5 on = gist_point_tbl using gist(p) with (buffering =3D = invalid_value);
 ERROR:  invalid value for option = "buffering": invalid_value
-DETAIL:  Valid values are = "on", "off", and "auto".
+DETAIL:  Valid values are "on", = "off", "true", "false", "yes", "no", "1", "0", and = "auto".
 create index gist_pointidx5 on gist_point_tbl = using gist(p) with (fillfactor=3D9);
 ERROR:  value = 9 out of bounds for option "fillfactor"
 DETAIL: =  Valid values are between "10" and = "100".

--- = a/src/test/regress/sql/gist.sql
+++ = b/src/test/regress/sql/gist.sql
@@ -11,7 +11,8 @@ create index = gist_pointidx on gist_point_tbl using gist(p);
 create = index gist_pointidx2 on gist_point_tbl using gist(p) with (buffering =3D = on, fillfactor=3D50);
 create index gist_pointidx3 on = gist_point_tbl using gist(p) with (buffering =3D = off);
 create index gist_pointidx4 on gist_point_tbl = using gist(p) with (buffering =3D auto);
-drop index = gist_pointidx2, gist_pointidx3, gist_pointidx4;
+create index = gist_pointidx4b on gist_point_tbl using gist(p) with (buffering =3D = true);
+drop index gist_pointidx2, gist_pointidx3, = gist_pointidx4, gist_pointidx4b;
```
Same idea for = GiST buffering: true should work as a synonym for on. Expected output = updated for the new index and the extended DETAIL line on invalid = input.


------
Regards,
Andrey Rachitskiy

= --Apple-Mail=_C84369F9-DF22-42A5-9F38-900AB9291ED4 Content-Disposition: attachment; filename=review_ternary-options-part2___v2.patch Content-Type: application/octet-stream; x-unix-mode=0644; name="review_ternary-options-part2___v2.patch" Content-Transfer-Encoding: 7bit diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 7094016a9c..b11220119d 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -915,7 +915,8 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, */ static relopt_ternary * init_ternary_reloption(uint32 kinds, const char *name, const char *desc, - pg_ternary default_val, const char* unset_alias, LOCKMODE lockmode) + pg_ternary default_val, const char *unset_alias, + LOCKMODE lockmode) { relopt_ternary *newoption; @@ -933,7 +934,8 @@ init_ternary_reloption(uint32 kinds, const char *name, const char *desc, */ void add_ternary_reloption(uint32 kinds, const char *name, const char *desc, - pg_ternary default_val, const char* unset_alias, LOCKMODE lockmode) + pg_ternary default_val, const char *unset_alias, + LOCKMODE lockmode) { relopt_ternary *newoption; @@ -952,7 +954,7 @@ add_ternary_reloption(uint32 kinds, const char *name, const char *desc, void add_local_ternary_reloption(local_relopts *relopts, const char *name, const char *desc, pg_ternary default_val, - const char* unset_alias, int offset) + const char *unset_alias, int offset) { relopt_ternary *newoption; @@ -1723,10 +1725,10 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, if (validate && !parsed) ereport(ERROR, (errcode(ERRCODE_INVALID_PARAMETER_VALUE), - errmsg("invalid value for option \"%s\": %s", + errmsg("invalid value for option \"%s\": %s", option->gen->name, value), - errdetail("Valid values are \"on\", \"off\", and \"%s\".", - opt->unset_alias))); + errdetail("Valid values are \"on\", \"off\", \"true\", \"false\", \"yes\", \"no\", \"1\", \"0\", and \"%s\".", + opt->unset_alias))); } break; case RELOPT_TYPE_INT: @@ -1901,7 +1903,8 @@ fillRelOptions(void *rdopts, Size basesize, break; case RELOPT_TYPE_TERNARY: *(pg_ternary *) itempos = options[i].isset ? - options[i].ternary_val : PG_TERNARY_UNSET; + options[i].ternary_val : + ((relopt_ternary *) options[i].gen)->default_val; break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? diff --git a/src/backend/access/gist/gistutil.c b/src/backend/access/gist/gistutil.c index 0f58f61879..2d69c3549b 100644 --- a/src/backend/access/gist/gistutil.c +++ b/src/backend/access/gist/gistutil.c @@ -913,7 +913,8 @@ gistoptions(Datum reloptions, bool validate) { static const relopt_parse_elt tab[] = { {"fillfactor", RELOPT_TYPE_INT, offsetof(GiSTOptions, fillfactor)}, - {"buffering", RELOPT_TYPE_ENUM, offsetof(GiSTOptions, buffering_mode)} + {"buffering", RELOPT_TYPE_TERNARY, + offsetof(GiSTOptions, buffering_mode)} }; return (bytea *) build_reloptions(reloptions, validate, diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index 1c5ea85e76..599a99ec2e 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -98,8 +98,8 @@ typedef struct relopt_bool typedef struct relopt_ternary { relopt_gen gen; - const char *unset_alias; /* word that will be treated as unset value */ - int default_val; + const char *unset_alias; /* word treated as unset, or NULL */ + pg_ternary default_val; } relopt_ternary; typedef struct relopt_int @@ -191,8 +191,8 @@ extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(uint32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); extern void add_ternary_reloption(uint32 kinds, const char *name, - const char *desc, pg_ternary default_val, - const char* unset_alias, LOCKMODE lockmode); + const char *desc, pg_ternary default_val, + const char *unset_alias, LOCKMODE lockmode); extern void add_int_reloption(uint32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -213,9 +213,9 @@ extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); extern void add_local_ternary_reloption(local_relopts *relopts, - const char *name, const char *desc, - pg_ternary default_val, const char* unset_alias, - int offset); + const char *name, const char *desc, + pg_ternary default_val, + const char *unset_alias, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/test/modules/dummy_index_am/expected/reloptions.out b/src/test/modules/dummy_index_am/expected/reloptions.out index 90abbe2e37..ea07de1d6e 100644 --- a/src/test/modules/dummy_index_am/expected/reloptions.out +++ b/src/test/modules/dummy_index_am/expected/reloptions.out @@ -123,7 +123,7 @@ ERROR: invalid value for boolean option "option_ternary_1": do_not_know_yet ALTER INDEX dummy_test_idx SET (option_ternary_2 = 'do_not_know_yet'); -- ok ALTER INDEX dummy_test_idx SET (option_ternary_2 = 'illegal_value'); -- error ERROR: invalid value for option "option_ternary_2": illegal_value -DETAIL: Valid values are "on", "off", and "do_not_know_yet". +DETAIL: Valid values are "on", "off", "true", "false", "yes", "no", "1", "0", and "do_not_know_yet". SELECT unnest(reloptions) FROM pg_class WHERE relname = 'dummy_test_idx'; unnest ---------------------------------- diff --git a/src/test/regress/expected/gist.out b/src/test/regress/expected/gist.out index 19dc547d5f..ea2acb8fbe 100644 --- a/src/test/regress/expected/gist.out +++ b/src/test/regress/expected/gist.out @@ -9,11 +9,12 @@ create index gist_pointidx on gist_point_tbl using gist(p); create index gist_pointidx2 on gist_point_tbl using gist(p) with (buffering = on, fillfactor=50); create index gist_pointidx3 on gist_point_tbl using gist(p) with (buffering = off); create index gist_pointidx4 on gist_point_tbl using gist(p) with (buffering = auto); -drop index gist_pointidx2, gist_pointidx3, gist_pointidx4; +create index gist_pointidx4b on gist_point_tbl using gist(p) with (buffering = true); +drop index gist_pointidx2, gist_pointidx3, gist_pointidx4, gist_pointidx4b; -- Make sure bad values are refused create index gist_pointidx5 on gist_point_tbl using gist(p) with (buffering = invalid_value); ERROR: invalid value for option "buffering": invalid_value -DETAIL: Valid values are "on", "off", and "auto". +DETAIL: Valid values are "on", "off", "true", "false", "yes", "no", "1", "0", and "auto". create index gist_pointidx5 on gist_point_tbl using gist(p) with (fillfactor=9); ERROR: value 9 out of bounds for option "fillfactor" DETAIL: Valid values are between "10" and "100". diff --git a/src/test/regress/expected/reloptions.out b/src/test/regress/expected/reloptions.out index 6e65cd5c3d..d85eb68380 100644 --- a/src/test/regress/expected/reloptions.out +++ b/src/test/regress/expected/reloptions.out @@ -134,6 +134,23 @@ SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; {vacuum_index_cleanup=auto} (1 row) +-- boolean synonyms are accepted +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=true); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +----------------------------- + {vacuum_index_cleanup=true} +(1 row) + +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=false); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +------------------------------ + {vacuum_index_cleanup=false} +(1 row) + -- Test vacuum_truncate option DROP TABLE reloptions_test; CREATE TEMP TABLE reloptions_test(i INT NOT NULL, j text) diff --git a/src/test/regress/sql/gist.sql b/src/test/regress/sql/gist.sql index 6f1fc65f12..0683574719 100644 --- a/src/test/regress/sql/gist.sql +++ b/src/test/regress/sql/gist.sql @@ -11,7 +11,8 @@ create index gist_pointidx on gist_point_tbl using gist(p); create index gist_pointidx2 on gist_point_tbl using gist(p) with (buffering = on, fillfactor=50); create index gist_pointidx3 on gist_point_tbl using gist(p) with (buffering = off); create index gist_pointidx4 on gist_point_tbl using gist(p) with (buffering = auto); -drop index gist_pointidx2, gist_pointidx3, gist_pointidx4; +create index gist_pointidx4b on gist_point_tbl using gist(p) with (buffering = true); +drop index gist_pointidx2, gist_pointidx3, gist_pointidx4, gist_pointidx4b; -- Make sure bad values are refused create index gist_pointidx5 on gist_point_tbl using gist(p) with (buffering = invalid_value); diff --git a/src/test/regress/sql/reloptions.sql b/src/test/regress/sql/reloptions.sql index c99673db9e..d4ba177090 100644 --- a/src/test/regress/sql/reloptions.sql +++ b/src/test/regress/sql/reloptions.sql @@ -80,6 +80,14 @@ DROP TABLE reloptions_test; CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=auto); SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; +-- boolean synonyms are accepted +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=true); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=false); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + -- Test vacuum_truncate option DROP TABLE reloptions_test; --Apple-Mail=_C84369F9-DF22-42A5-9F38-900AB9291ED4 Content-Transfer-Encoding: quoted-printable Content-Type: text/html; charset=utf-8

18 = =D0=BC=D0=B0=D1=8F 2026=E2=80=AF=D0=B3., =D0=B2 19:11, Nikolay Shaplov = <dhyan@nataraj.su> =D0=BD=D0=B0=D0=BF=D0=B8=D1=81=D0=B0=D0=BB(=D0=B0= ):

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

=D0=9E=D1=82=D0=BF=D1=80=D0=B0=D0=B2=D0=B8=D1=82=D0=B5=D0=BB=D1=8C= : Nikolay Shaplov = <dhyan@nataraj.su>
=D0=A2=D0=B5=D0=BC=D0=B0: = =D0=9E=D1=82=D0=B2=D0=B5=D1=82: [PATCH] = ternary reloption type
=D0=94=D0=B0=D1=82=D0=B0: = 10 =D0=BC=D0=B0=D1=8F 2026=E2=80=AF=D0=B3. = =D0=B2 16:09:28 GMT+5
=D0=9A=D0=BE=D0=BC=D1=83: = =C3=81lvaro Herrera = <alvherre@alvh.no-ip.org>, = pgsql-hackers@lists.postgresql.org
=D0=9A=D0=BE=D0=BF=D0=B8=D1=8F: PostgreSQL Hackers = <pgsql-hackers@lists.postgresql.org>, Chris Travers = <chris.travers@gmail.com>, Timur Magomedov = <t.magomedov@postgrespro.ru>, Nathan Bossart = <nathandbossart@gmail.com>, Nikolay Shaplov = <dhyan@nataraj.su>


=D0=92 =D0=BF=D0=B8=D1=81= =D1=8C=D0=BC=D0=B5 =D0=BE=D1=82 =D1=87=D0=B5=D1=82=D0=B2=D0=B5=D1=80=D0=B3= , 5 =D1=84=D0=B5=D0=B2=D1=80=D0=B0=D0=BB=D1=8F 2026=E2=80=AF=D0=B3. = 16:08:59 =D0=9C=D0=BE=D1=81=D0=BA=D0=B2=D0=B0, =D1=81=D1=82=D0=B0=D0=BD=D0= =B4=D0=B0=D1=80=D1=82=D0=BD=D0=BE=D0=B5 =D0=B2=D1=80=D0=B5=D0=BC=D1=8F =
=D0=BF=D0=BE=D0=BB=D1=8C=D0=B7=D0=BE=D0=B2=D0=B0=D1=82=D0=B5=D0=BB=D1=8C= Nikolay Shaplov =D0=BD=D0=B0=D0=BF=D0=B8=D1=81=D0=B0=D0=BB:

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
= --Apple-Mail=_C84369F9-DF22-42A5-9F38-900AB9291ED4 Content-Disposition: attachment; filename=v2-0001-Convert-vacuum_index_cleanup-and-GiST-s-buffering.patch Content-Type: text/x-patch; x-unix-mode=0666; name="v2-0001-Convert-vacuum_index_cleanup-and-GiST-s-buffering.patch" Content-Transfer-Encoding: 7bit -----BEGIN PGP SIGNATURE----- iQEzBAABCgAdFiEE+sk3ebqQKlezKOi8PMbfuIHAGpgFAmoAZ2gACgkQPMbfuIHA GpirwwgAnT+MgmIE5m1ayaChqoJfg0n4gzUrk5r4Mo7FVDBt+NgYvHwsV6NCtxC4 fPuprZV17jGDQPaJ0HKFtuOmJqa3ozoTzFm4n6l/rn5x3HV6IBrPGwy3X9QV57Wk cvhTBSzgyy7GhJB20718fbgaFoUnUhI/ieQODJzPecbdUdgle6bn6gnkRiUK72Kr /Hu8qc15vuyh/GLJUINAM/m2goNVTEb6mVcZh6zoV1FZfGYvw4X5+sNO+beg8NkW 9UZUJkIr0qolAPS1zstrPHk0MG5YfL2PR54AK7wXCT2ZUQgtlks3ghvbZINMCEmO x1AFchU+EQSS4RzdiSF5NgIcJYfpsw== =Qsjj -----END PGP SIGNATURE----- --Apple-Mail=_C84369F9-DF22-42A5-9F38-900AB9291ED4 Content-Transfer-Encoding: 7bit Content-Type: text/html; charset=us-ascii



--Apple-Mail=_C84369F9-DF22-42A5-9F38-900AB9291ED4-- --Apple-Mail=_18AE1723-4D48-494F-8B4C-6DAD13E91C24--