postgres.git / summary / log / commit / refs

commit    a625fc570c22e199471e1a2656e2c32b8dc0c0fd
Author:   Michael Paquier <michael@paquier.xyz>
Date:     Mon Sep 14 23:37:06 2026 +0000

    Add support for toast_value_type=oid8
    
    This commit adds the possibility to define TOAST tables with oid8 as
    their chunk_id, based on the reloption toast_value_type.  All the
    external TOAST pointers still rely on varatt_external_oid and a single
    vartag.  The values inserted in the oid8 TOAST tables are fed from the
    OID8 value generator, casted to OID for now, as we do not have a
    vartag_external able to store Oid8 values yet.
    
    An upcoming commit will add support for a new vartag_external and its
    associated structures, with the code being able to use a different
    on-disk external TOAST pointer depending on the attribute type of
    chunk_id defined in TOAST relations.
    
    All the changes done here are mechanical, mostly around the TOAST code
    that needs to be able to do chunk_id lookups based on the two types now
    supported.
    
    Bump catalog version.
    
    Author: Michael Paquier <michael@paquier.xyz>
    Reviewed-by: Greg Burd <greg@burd.me>
    Reviewed-by: Bharath Rupireddy <bharath.rupireddyforpostgres@gmail.com>
    Reviewed-by: Yugo Nagata <nagata@sraoss.co.jp>
    Discussion: https://postgr.es/m/af19kUjwjhaoUTLn@paquier.xy


contrib/amcheck/verify_heapam.c | 13 ++-- doc/src/sgml/ref/create_table.sgml | 5 +- doc/src/sgml/storage.sgml | 7 +- src/backend/access/common/reloptions.c | 3 +- src/backend/access/common/toast_internals.c | 88 +++++++++++++++++-------- src/backend/access/heap/heaptoast.c | 7 +- src/backend/catalog/toasting.c | 25 ++++++- src/backend/replication/logical/reorderbuffer.c | 9 ++- src/include/access/toast_internals.h | 4 ++ src/include/catalog/pg_opclass.dat | 3 +- src/include/utils/rel.h | 1 + src/test/regress/expected/reloptions.out | 15 ++++- src/test/regress/sql/reloptions.sql | 8 +++ 13 files changed, 142 insertions(+), 46 deletions(-) diff --git a/contrib/amcheck/verify_heapam.c b/contrib/amcheck/verify_heapam.c index 33b00fa4dc5..56167ecfe1d 100644 --- a/contrib/amcheck/verify_heapam.c +++ b/contrib/amcheck/verify_heapam.c @@ -28,7 +28,6 @@ #include "storage/procarray.h" #include "storage/read_stream.h" #include "utils/builtins.h" -#include "utils/fmgroids.h" #include "utils/rel.h" #include "utils/tuplestore.h" @@ -1876,8 +1875,12 @@ check_toasted_attribute(HeapCheckContext *ctx, ToastedAttribute *ta) uint32 extsize; int32 expected_chunk_seq = 0; int32 last_chunk_seq; - int32 max_chunk_size = TOAST_OID_MAX_CHUNK_SIZE; + int32 max_chunk_size; Oid8 toast_valueid; + Oid toast_typid; + + toast_typid = TupleDescAttr(ctx->toast_rel->rd_att, 0)->atttypid; + max_chunk_size = TOAST_OID_MAX_CHUNK_SIZE; extsize = VARATT_EXTERNAL_OID_GET_EXTSIZE(ta->toast_pointer); last_chunk_seq = (extsize - 1) / max_chunk_size; @@ -1885,10 +1888,8 @@ check_toasted_attribute(HeapCheckContext *ctx, ToastedAttribute *ta) /* * Setup a scan key to find chunks in toast table with matching va_valueid */ - ScanKeyInit(&toastkey, - (AttrNumber) 1, - BTEqualStrategyNumber, F_OIDEQ, - ObjectIdGetDatum(ta->toast_pointer.va_valueid)); + toast_valueid_scankey_init(&toastkey, toast_typid, + ta->toast_pointer.va_valueid); /* * Check if any chunks for this toasted object exist in the toast table, diff --git a/doc/src/sgml/ref/create_table.sgml b/doc/src/sgml/ref/create_table.sgml index 99b7afa23d2..fef24d8f3a2 100644 --- a/doc/src/sgml/ref/create_table.sgml +++ b/doc/src/sgml/ref/create_table.sgml @@ -1663,7 +1663,10 @@ WITH ( MODULUS <replaceable class="parameter">numeric_literal</replaceable>, REM <para> Specifies the attribute type of <literal>chunk_id</literal> to use when creating a <acronym>TOAST</acronym> relation for this table. The - default is <literal>oid</literal>. + default is <literal>oid</literal>; <literal>oid8</literal> assigns + <type>oid8</type> instead, which allows a larger number of distinct + <acronym>TOAST</acronym>ed values at the price of four extra bytes per + out-of-line pointer. This parameter cannot be set for <acronym>TOAST</acronym> tables. </para> <para> diff --git a/doc/src/sgml/storage.sgml b/doc/src/sgml/storage.sgml index 90aae3defcb..21bd67c11ea 100644 --- a/doc/src/sgml/storage.sgml +++ b/doc/src/sgml/storage.sgml @@ -421,14 +421,15 @@ most <symbol>TOAST_OID_MAX_CHUNK_SIZE</symbol> bytes (by default this value is c so that four chunk rows will fit on a page, making it about 2000 bytes). Each chunk is stored as a separate row in the <acronym>TOAST</acronym> table belonging to the owning table. Every -<acronym>TOAST</acronym> table has the columns <structfield>chunk_id</structfield> (an OID -identifying the particular <acronym>TOAST</acronym>ed value), +<acronym>TOAST</acronym> table has the columns +<structfield>chunk_id</structfield> (an OID or an OID8 identifying +the particular <acronym>TOAST</acronym>ed value), <structfield>chunk_seq</structfield> (a sequence number for the chunk within its value), and <structfield>chunk_data</structfield> (the actual data of the chunk). A unique index on <structfield>chunk_id</structfield> and <structfield>chunk_seq</structfield> provides fast retrieval of the values. A pointer datum representing an out-of-line on-disk <acronym>TOAST</acronym>ed value therefore needs to store the OID of the -<acronym>TOAST</acronym> table in which to look and the OID of the specific value +<acronym>TOAST</acronym> table in which to look and the specific value (its <structfield>chunk_id</structfield>). For convenience, pointer datums also store the logical datum size (original uncompressed data length), physical stored size (different if compression was applied), and the compression method used, if diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 82491a31a9a..ea9a0417909 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -553,6 +553,7 @@ static relopt_enum_elt_def StdRdOptToastValueTypes[] = { /* no value for INVALID */ {"oid", STDRD_OPTION_TOAST_VALUE_TYPE_OID}, + {"oid8", STDRD_OPTION_TOAST_VALUE_TYPE_OID8}, {(const char *) NULL} /* list terminator */ }; @@ -578,7 +579,7 @@ static relopt_enum enumRelOpts[] = }, StdRdOptToastValueTypes, STDRD_OPTION_TOAST_VALUE_TYPE_OID, - gettext_noop("Valid values are \"oid\".") + gettext_noop("Valid values are \"oid\" and \"oid8\".") }, { { diff --git a/src/backend/access/common/toast_internals.c b/src/backend/access/common/toast_internals.c index ca5ed8dfabc..2b7c6433bb4 100644 --- a/src/backend/access/common/toast_internals.c +++ b/src/backend/access/common/toast_internals.c @@ -25,6 +25,7 @@ #include "utils/fmgroids.h" #include "utils/rel.h" #include "utils/snapmgr.h" +#include "utils/lsyscache.h" static bool toastrel_valueid_exists(Relation toastrel, Oid8 valueid); static bool toastid_valueid_exists(Oid toastrelid, Oid8 valueid); @@ -131,6 +132,7 @@ toast_save_datum(Relation rel, Datum value, Pointer dval = DatumGetPointer(value); int num_indexes; int validIndex; + Oid toast_typid = get_atttype(rel->rd_rel->reltoastrelid, 1); Assert(!VARATT_IS_EXTERNAL(dval)); @@ -200,24 +202,31 @@ toast_save_datum(Relation rel, Datum value, toast_pointer.va_toastrelid = RelationGetRelid(toastrel); /* - * Choose an OID to use as the value ID for this toast value. + * Choose a new value to use as the value ID for this toast value, be it + * for OID or OID8 TOAST relations. * - * Normally we just choose an unused OID within the toast table. But + * Normally we just choose an unused value within the toast table. But * during table-rewriting operations where we are preserving an existing - * toast table OID, we want to preserve toast value OIDs too. So, if + * toast table OID, we want to preserve toast value IDs too. So, if * rd_toastoid is set and we had a prior external value from that same * toast table, re-use its value ID. If we didn't have a prior external * value (which is a corner case, but possible if the table's attstorage * options have been changed), we have to pick a value ID that doesn't - * conflict with either new or existing toast value OIDs. + * conflict with either new or existing toast value IDs. If the TOAST + * table uses 8-byte value IDs, we should not really care much about that. */ if (!OidIsValid(rel->rd_toastoid)) { /* normal case: just choose an unused OID */ - toast_pointer.va_valueid = - GetNewOidWithIndex(toastrel, - RelationGetRelid(toastidxs[validIndex]), - (AttrNumber) 1); + if (toast_typid == OID8OID) + toast_pointer.va_valueid = GetNewObjectId8(); + else + { + toast_pointer.va_valueid = + GetNewOidWithIndex(toastrel, + RelationGetRelid(toastidxs[validIndex]), + (AttrNumber) 1); + } } else { @@ -263,17 +272,22 @@ toast_save_datum(Relation rel, Datum value, if (toast_pointer.va_valueid == InvalidOid) { /* - * new value; must choose an OID that doesn't conflict in either - * old or new toast table + * new value; must choose a value that doesn't conflict in either + * old or new toast table. */ - do + if (toast_typid == OID8OID) + toast_pointer.va_valueid = GetNewObjectId8(); + else { - toast_pointer.va_valueid = - GetNewOidWithIndex(toastrel, - RelationGetRelid(toastidxs[validIndex]), - (AttrNumber) 1); - } while (toastid_valueid_exists(rel->rd_toastoid, - toast_pointer.va_valueid)); + do + { + toast_pointer.va_valueid = + GetNewOidWithIndex(toastrel, + RelationGetRelid(toastidxs[validIndex]), + (AttrNumber) 1); + } while (toastid_valueid_exists(rel->rd_toastoid, + toast_pointer.va_valueid)); + } } } @@ -303,7 +317,10 @@ toast_save_datum(Relation rel, Datum value, /* * Build a tuple and store it */ - t_values[0] = ObjectIdGetDatum(toast_pointer.va_valueid); + if (toast_typid == OID8OID) + t_values[0] = ObjectId8GetDatum(toast_pointer.va_valueid); + else + t_values[0] = ObjectIdGetDatum(toast_pointer.va_valueid); t_values[1] = Int32GetDatum(chunk_seq++); SET_VARSIZE(&chunk_data, chunk_size + VARHDRSZ); memcpy(VARDATA(&chunk_data), data_p, chunk_size); @@ -366,6 +383,26 @@ toast_save_datum(Relation rel, Datum value, return PointerGetDatum(result); } +/* ---------- + * toast_valueid_scankey_init - + * + * Initialize a scan key that matches the value ID column of a TOAST table. + * ---------- + */ +void +toast_valueid_scankey_init(ScanKey entry, Oid toast_typid, Oid8 valueid) +{ + Assert(toast_typid == OIDOID || toast_typid == OID8OID); + if (toast_typid == OID8OID) + ScanKeyInit(entry, (AttrNumber) 1, + BTEqualStrategyNumber, F_OID8EQ, + ObjectId8GetDatum(valueid)); + else + ScanKeyInit(entry, (AttrNumber) 1, + BTEqualStrategyNumber, F_OIDEQ, + ObjectIdGetDatum((Oid) valueid)); +} + /* ---------- * toast_delete_datum - * @@ -405,10 +442,9 @@ toast_delete_datum(Relation rel, Datum value, bool is_speculative) /* * Setup a scan key to find chunks with matching va_valueid */ - ScanKeyInit(&toastkey, - (AttrNumber) 1, - BTEqualStrategyNumber, F_OIDEQ, - ObjectIdGetDatum(toast_pointer.va_valueid)); + toast_valueid_scankey_init(&toastkey, + TupleDescAttr(toastrel->rd_att, 0)->atttypid, + toast_pointer.va_valueid); /* * Find all the chunks. (We don't actually care whether we see them in @@ -455,6 +491,7 @@ toastrel_valueid_exists(Relation toastrel, Oid8 valueid) int num_indexes; int validIndex; Relation *toastidxs; + Oid toast_typid; /* Fetch a valid index relation */ validIndex = toast_open_indexes(toastrel, @@ -462,13 +499,12 @@ toastrel_valueid_exists(Relation toastrel, Oid8 valueid) &toastidxs, &num_indexes); + toast_typid = TupleDescAttr(toastrel->rd_att, 0)->atttypid; + /* * Setup a scan key to find chunks with matching va_valueid */ - ScanKeyInit(&toastkey, - (AttrNumber) 1, - BTEqualStrategyNumber, F_OIDEQ, - ObjectIdGetDatum(valueid)); + toast_valueid_scankey_init(&toastkey, toast_typid, valueid); /* * Is there any such chunk? diff --git a/src/backend/access/heap/heaptoast.c b/src/backend/access/heap/heaptoast.c index 81154c17376..81d5085ca39 100644 --- a/src/backend/access/heap/heaptoast.c +++ b/src/backend/access/heap/heaptoast.c @@ -640,6 +640,7 @@ heap_fetch_toast_slice(Relation toastrel, Oid8 valueid, int32 attrsize, int num_indexes; int validIndex; int32 max_chunk_size; + Oid toast_typid; /* Look for the valid index of toast relation */ validIndex = toast_open_indexes(toastrel, @@ -647,6 +648,7 @@ heap_fetch_toast_slice(Relation toastrel, Oid8 valueid, int32 attrsize, &toastidxs, &num_indexes); + toast_typid = TupleDescAttr(toastrel->rd_att, 0)->atttypid; max_chunk_size = TOAST_OID_MAX_CHUNK_SIZE; totalchunks = ((attrsize - 1) / max_chunk_size) + 1; @@ -655,10 +657,7 @@ heap_fetch_toast_slice(Relation toastrel, Oid8 valueid, int32 attrsize, Assert(endchunk <= totalchunks); /* Set up a scan key to fetch from the index. */ - ScanKeyInit(&toastkey[0], - (AttrNumber) 1, - BTEqualStrategyNumber, F_OIDEQ, - ObjectIdGetDatum(valueid)); + toast_valueid_scankey_init(&toastkey[0], toast_typid, valueid); /* * No additional condition if fetching all chunks. Otherwise, use an diff --git a/src/backend/catalog/toasting.c b/src/backend/catalog/toasting.c index a4a7b7c91b3..66dbbae66e2 100644 --- a/src/backend/catalog/toasting.c +++ b/src/backend/catalog/toasting.c @@ -32,6 +32,7 @@ #include "nodes/makefuncs.h" #include "utils/fmgroids.h" #include "utils/rel.h" +#include "utils/lsyscache.h" #include "utils/syscache.h" static void CheckAndCreateToastTable(Oid relOid, Datum reloptions, @@ -173,6 +174,9 @@ create_toast_table(Relation rel, Oid toastOid, Oid toastIndexOid, case STDRD_OPTION_TOAST_VALUE_TYPE_OID: toast_chunkid_typid = OIDOID; break; + case STDRD_OPTION_TOAST_VALUE_TYPE_OID8: + toast_chunkid_typid = OID8OID; + break; case STDRD_OPTION_TOAST_VALUE_TYPE_INVALID: elog(ERROR, "unexpected toast_value_type value %d", value_type); @@ -210,7 +214,8 @@ create_toast_table(Relation rel, Oid toastOid, Oid toastIndexOid, ereport(ERROR, (errcode(ERRCODE_INVALID_PARAMETER_VALUE), errmsg("toast chunk_id type not set while in binary upgrade mode"))); - if (binary_upgrade_next_toast_chunk_id_typoid != OIDOID) + if (binary_upgrade_next_toast_chunk_id_typoid != OIDOID && + binary_upgrade_next_toast_chunk_id_typoid != OID8OID) ereport(ERROR, (errcode(ERRCODE_INVALID_PARAMETER_VALUE), errmsg("cannot support toast chunk_id type %u in binary upgrade mode", @@ -235,6 +240,19 @@ create_toast_table(Relation rel, Oid toastOid, Oid toastIndexOid, snprintf(toast_idxname, sizeof(toast_idxname), "pg_toast_%u_index", relOid); + /* + * Special case here. If OIDOldToast is defined, rely on the existing + * TOAST table and its chunk_id type, not the reloption value. This + * guarantees that the same TOAST table is kept across rewrites of the + * parent. + */ + if (OidIsValid(OIDOldToast)) + { + toast_chunkid_typid = get_atttype(OIDOldToast, 1); + if (!OidIsValid(toast_chunkid_typid)) + elog(ERROR, "cache lookup failed for relation %u", OIDOldToast); + } + /* this is pretty painful... need a tuple descriptor */ tupdesc = CreateTemplateTupleDesc(3); TupleDescInitEntry(tupdesc, (AttrNumber) 1, @@ -353,7 +371,10 @@ create_toast_table(Relation rel, Oid toastOid, Oid toastIndexOid, collationIds[0] = InvalidOid; collationIds[1] = InvalidOid; - opclassIds[0] = OID_BTREE_OPS_OID; + if (toast_chunkid_typid == OID8OID) + opclassIds[0] = OID8_BTREE_OPS_OID; + else + opclassIds[0] = OID_BTREE_OPS_OID; opclassIds[1] = INT4_BTREE_OPS_OID; coloptions[0] = 0; diff --git a/src/backend/replication/logical/reorderbuffer.c b/src/backend/replication/logical/reorderbuffer.c index 534fe338b38..dd4b6e8f7f4 100644 --- a/src/backend/replication/logical/reorderbuffer.c +++ b/src/backend/replication/logical/reorderbuffer.c @@ -5036,6 +5036,8 @@ ReorderBufferToastAppendChunk(ReorderBuffer *rb, ReorderBufferTXN *txn, TupleDesc desc = RelationGetDescr(relation); Oid8 chunk_id; int32 chunk_seq; + Oid valueid_type; + Datum valueid_datum; if (txn->toast_hash == NULL) ReorderBufferToastInitHash(rb, txn); @@ -5043,7 +5045,12 @@ ReorderBufferToastAppendChunk(ReorderBuffer *rb, ReorderBufferTXN *txn, Assert(IsToastRelation(relation)); newtup = change->data.tp.newtuple; - chunk_id = DatumGetObjectId(fastgetattr(newtup, 1, desc, &isnull)); + valueid_type = TupleDescAttr(desc, 0)->atttypid; + valueid_datum = fastgetattr(newtup, 1, desc, &isnull); + if (valueid_type == OID8OID) + chunk_id = DatumGetObjectId8(valueid_datum); + else + chunk_id = DatumGetObjectId(valueid_datum); Assert(!isnull); chunk_seq = DatumGetInt32(fastgetattr(newtup, 2, desc, &isnull)); Assert(!isnull); diff --git a/src/include/access/toast_internals.h b/src/include/access/toast_internals.h index bf45889a642..e03cc1204be 100644 --- a/src/include/access/toast_internals.h +++ b/src/include/access/toast_internals.h @@ -12,6 +12,7 @@ #ifndef TOAST_INTERNALS_H #define TOAST_INTERNALS_H +#include "access/skey.h" #include "access/toast_compression.h" #include "storage/lockdefs.h" #include "utils/relcache.h" @@ -52,6 +53,9 @@ extern void toast_delete_datum(Relation rel, Datum value, bool is_speculative); extern Datum toast_save_datum(Relation rel, Datum value, varlena *oldexternal, uint32 options); +extern void toast_valueid_scankey_init(ScanKey entry, Oid toast_typid, + Oid8 valueid); + extern int toast_open_indexes(Relation toastrel, LOCKMODE lock, Relation **toastidxs, diff --git a/src/include/catalog/pg_opclass.dat b/src/include/catalog/pg_opclass.dat index df170b80840..b84c2bb7a8c 100644 --- a/src/include/catalog/pg_opclass.dat +++ b/src/include/catalog/pg_opclass.dat @@ -179,7 +179,8 @@ opcintype => 'xid8' }, { opcmethod => 'hash', opcname => 'oid8_ops', opcfamily => 'hash/oid8_ops', opcintype => 'oid8' }, -{ opcmethod => 'btree', opcname => 'oid8_ops', opcfamily => 'btree/oid8_ops', +{ oid => '8285', oid_symbol => 'OID8_BTREE_OPS_OID', + opcmethod => 'btree', opcname => 'oid8_ops', opcfamily => 'btree/oid8_ops', opcintype => 'oid8' }, { opcmethod => 'hash', opcname => 'cid_ops', opcfamily => 'hash/cid_ops', opcintype => 'cid' }, diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index a88549dcc55..d61432de705 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -346,6 +346,7 @@ typedef enum StdRdOptToastValueType { STDRD_OPTION_TOAST_VALUE_TYPE_INVALID = 0, STDRD_OPTION_TOAST_VALUE_TYPE_OID, + STDRD_OPTION_TOAST_VALUE_TYPE_OID8, } StdRdOptToastValueType; typedef struct StdRdOptions diff --git a/src/test/regress/expected/reloptions.out b/src/test/regress/expected/reloptions.out index 9b4c8587aa4..95e4528f779 100644 --- a/src/test/regress/expected/reloptions.out +++ b/src/test/regress/expected/reloptions.out @@ -199,7 +199,7 @@ CREATE TABLE reloptions_test2 (s VARCHAR) WITH (toast.toast_value_type = 'oid'); ERROR: unrecognized parameter "toast_value_type" CREATE TABLE reloptions_test2 (s VARCHAR) WITH (toast_value_type = 'int8'); ERROR: invalid value for enum option "toast_value_type": int8 -DETAIL: Valid values are "oid". +DETAIL: Valid values are "oid" and "oid8". CREATE TABLE reloptions_test2 (s VARCHAR) WITH (toast_value_type = 'oid'); SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test2'::regclass; reloptions @@ -207,6 +207,19 @@ SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test2'::regclass; {toast_value_type=oid} (1 row) +-- The option is only consulted when the TOAST relation is created, so +-- changing it afterwards leaves chunk_id alone. +ALTER TABLE reloptions_test2 SET (toast_value_type = 'oid8'); +SELECT a.atttypid::regtype AS chunk_id_type + FROM pg_class AS c, pg_attribute AS a + WHERE c.oid = 'reloptions_test2'::regclass AND + a.attrelid = c.reltoastrelid AND a.attname = 'chunk_id'; + chunk_id_type +--------------- + oid +(1 row) + +ALTER TABLE reloptions_test2 RESET (toast_value_type); DROP TABLE reloptions_test2; -- relkinds not supported. CREATE INDEX reloptions_test_idx0 ON reloptions_test (s) diff --git a/src/test/regress/sql/reloptions.sql b/src/test/regress/sql/reloptions.sql index 503d15d16e5..80939ee8b5b 100644 --- a/src/test/regress/sql/reloptions.sql +++ b/src/test/regress/sql/reloptions.sql @@ -117,6 +117,14 @@ CREATE TABLE reloptions_test2 (s VARCHAR) WITH (toast.toast_value_type = 'oid'); CREATE TABLE reloptions_test2 (s VARCHAR) WITH (toast_value_type = 'int8'); CREATE TABLE reloptions_test2 (s VARCHAR) WITH (toast_value_type = 'oid'); SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test2'::regclass; +-- The option is only consulted when the TOAST relation is created, so +-- changing it afterwards leaves chunk_id alone. +ALTER TABLE reloptions_test2 SET (toast_value_type = 'oid8'); +SELECT a.atttypid::regtype AS chunk_id_type + FROM pg_class AS c, pg_attribute AS a + WHERE c.oid = 'reloptions_test2'::regclass AND + a.attrelid = c.reltoastrelid AND a.attname = 'chunk_id'; +ALTER TABLE reloptions_test2 RESET (toast_value_type); DROP TABLE reloptions_test2; -- relkinds not supported. CREATE INDEX reloptions_test_idx0 ON reloptions_test (s)